5.4 NOI模拟
\(5.4\ NOI\)模拟
\(T1\)
想到分讨,但是暴力输出一下方案之后有很多特别的情况要讨论,就弃了...
假设\(a\)是原序列,\(b\)是我们得到的序列
设\(i\)是最长公共前缀,\(j\)是最长公共后缀
我们假设询问的是整个序列,若\(i+j=n-1\)那我们的方案数是\(m-1\),较为显然
否则\(i+j<=n-2\)
首先比较显然的是,由于已知串确定,可以根据最长公共前后缀长度进行分类,即可不重不漏的计算每一种情况
那么\(a[i+1]\)和\(a[n-j]\)至少有一个出现在最长公共序列(可能取出来的公共序列不是同一个)里
我们枚举\(i,j\)
假设\(i+j<=n-2\)
\(Sit_1:a[i+1]\neq a[i+2]\)可以增加\(m-1\)种情况
\(Sit_2:a[n-j]\neq a[n-j-1]\)可以增加\(m-1\)种情况
\(Sit_3:a[i+1]\neq a[i+2]\)且\(a[n-j]\neq a[n-j-1]\)那么就\(a[i+1...n-j-2]=a[i+3...n-j],\)可增加\(1\)种情况
我们的答案是\(Sit_1+Sit_2-Sit_3\)
就是说我们可能出现\(1,2\)重复计算情况减去一部分就好了
那么暴力的话可以枚举\(i,j\)进行计算,可以发现,前两种情况,我们可以合并统计
对于第三种情况,我们只需要找到所有满足条件的\(i,j\)即可
那么枚举\(i\)那么\(j>=n-i-2-lcp(a[i+1..n],a[i+3...n])\)既满足\(a[n-j]\neq a[n-j-1],\)用\(Sit_3\)反证即可
合并的意思是,还是说 颜色块数\(\times n\times (m-1)\) 就好了,就没有那么多的分讨了
\(lcp\)求和的话发现有单调性,可使用双指针,复杂度降到\(O(n)\)
\(T2\)
\(Give\ up\)
教练说必须过,就写了一个下午加一个晚上
#include<bits/stdc++.h>
#define MAXN 18900005
#define MAXM 200005
#define ll long long
using namespace std;
const int mod=998244353,INF=1000000000;
int my_pow(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)
{
res=(1ll*res*a)%mod;
}
a=(1ll*a*a)%mod;
b>>=1;
}
return res;
}
namespace Seg
{
int cnt=1;
queue<int> rub;
struct node
{
int l,r,siz,sum,mul;
}tr[MAXN];
int New()
{
if(rub.size()==0) return cnt++;
else
{
int id=rub.front();
rub.pop();
tr[id].sum=tr[id].siz=tr[id].l=tr[id].r=0;
tr[id].mul=1;
return id;
}
}
void Init(int x)
{
tr[x].l=tr[x].r=tr[x].siz=tr[x].sum=0;
tr[x].mul=1;
}
void Del(int x)
{
if(x==0) return ;
rub.push(x);
Del(tr[x].l);
Del(tr[x].r);
}
void push_up(int x)
{
((tr[x].sum=(tr[tr[x].l].sum+tr[tr[x].r].sum)%mod)+=mod)%=mod;
tr[x].siz=(tr[tr[x].l].siz+tr[tr[x].r].siz);
((tr[x].mul=(1ll*tr[tr[x].l].mul*tr[tr[x].r].mul)%mod)+=mod)%=mod;
}
void change(int &x,int l,int r,ll poz,ll k)
{
x=New();
tr[x].sum=((k*poz%mod)+mod)%mod;
tr[x].mul=my_pow(poz,k);
tr[x].siz=k;
if(l==r) return ;
int mid=(l+r)>>1;
if(poz<=mid) change(tr[x].l,l,mid,poz,k);
else change(tr[x].r,mid+1,r,poz,k);
}
int Merge(int x,int y,int l,int r)
{
if(x==0||y==0) return x+y;
tr[x].sum=(tr[x].sum+tr[y].sum)%mod;
tr[x].mul=(1ll*tr[x].mul*tr[y].mul)%mod;
tr[x].siz=tr[x].siz+tr[y].siz;
if(l==r)
{
rub.push(y);
return x;
}
int mid=(l+r)>>1;
tr[x].l=Merge(tr[x].l,tr[y].l,l,mid);
tr[x].r=Merge(tr[x].r,tr[y].r,mid+1,r);
rub.push(y);
return x;
}
void spilt(int x,int l,int r,int k,int &rt1,int &rt2)
{
if(x==0)
{
rt1=rt2=0;
return ;
}
if(l==r)
{
if(k==0) rt1=0,rt2=x;
else if(k==tr[x].siz) rt1=x,rt2=0;
else
{
rt1=x;
rt2=New();
tr[rt2].siz=tr[x].siz-k;
tr[rt2].sum=(1ll*l*tr[rt2].siz%mod+mod)%mod;
tr[rt2].mul=my_pow(l,tr[rt2].siz);
tr[rt1].siz=k;
tr[rt1].sum=(1ll*l*tr[rt1].siz%mod+mod)%mod;
tr[rt1].mul=my_pow(l,tr[rt1].siz);
}
return ;
}
int mid=(l+r)>>1;
if(tr[tr[x].l].siz<=k)
{
rt1=x;
rt2=New();
spilt(tr[x].r,mid+1,r,k-tr[tr[x].l].siz,tr[rt1].r,tr[rt2].r);
}
else
{
rt1=New();
rt2=x;
spilt(tr[x].l,l,mid,k,tr[rt1].l,tr[rt2].l);
}
push_up(rt1); push_up(rt2);
}
int Get_Max(int x,int l,int r)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(tr[tr[x].r].siz) return Get_Max(tr[x].r,mid+1,r);
return Get_Max(tr[x].l,l,mid);
}
int Get_Min(int x,int l,int r)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(tr[tr[x].l].siz) return Get_Min(tr[x].l,l,mid);
return Get_Min(tr[x].r,mid+1,r);
}
}
namespace FHQ
{
int cnt=1;
int rt=0;
vector<int>rub;
struct node
{
int l,r,siz,key,rt[2];;
bool tag,rtag,ntag;
}tr[MAXM];
int New()
{
if(rub.size()==0) return cnt++;
else
{
int id=rub.back();
rub.pop_back();
tr[id].key=rand();
tr[id].l=tr[id].r=tr[id].siz=tr[id].rt[0]=tr[id].rt[1]=0;
tr[id].tag=tr[id].rtag=tr[id].ntag=0;
return id;
}
}
int New(int x,int k)
{
int id;
if(rub.size()==0) id=cnt++;
else id=rub.back(),rub.pop_back();
tr[id].key=rand();
tr[id].l=tr[id].r=0;
tr[id].tag=tr[id].rtag=tr[id].ntag=0;
tr[id].siz=k;
Seg::change(tr[id].rt[0],-INF,INF,x,k);
Seg::change(tr[id].rt[1],-INF,INF,-x,k);
return id;
}
void Del(int x)
{
if(x==0) return;
Seg::Del(tr[x].rt[0]);
Seg::Del(tr[x].rt[1]);
rub.push_back(x);
Del(tr[x].l);
Del(tr[x].r);
}
void push_up(int x)
{
tr[x].siz=(tr[tr[x].l].siz+tr[tr[x].r].siz+Seg::tr[tr[x].rt[0]].siz);
}
void Rev(int x)
{
if(x==0) return ;
tr[x].rtag^=1;
tr[x].tag^=1;
swap(tr[x].l,tr[x].r);
}
void Neg(int x)
{
if(x==0) return ;
tr[x].ntag^=1;
tr[x].tag^=1;
swap(tr[x].rt[0],tr[x].rt[1]);
}
void pd(int x)
{
if(tr[x].rtag)
{
Rev(tr[x].l);
Rev(tr[x].r);
tr[x].rtag=0;
}
if(tr[x].ntag)
{
Neg(tr[x].l);
Neg(tr[x].r);
tr[x].ntag=0;
}
}
void spilt(int rt,int k,int &rt1,int &rt2)
{
if(rt==0)
{
rt1=0; rt2=0;
return ;
}
pd(rt);
if(tr[tr[rt].l].siz>=k)
{
rt2=rt;
spilt(tr[rt].l,k,rt1,tr[rt2].l);
push_up(rt2);
}
else if(tr[tr[rt].l].siz+Seg::tr[tr[rt].rt[0]].siz<=k)
{
rt1=rt;
spilt(tr[rt].r,k-(tr[tr[rt].l].siz+Seg::tr[tr[rt].rt[0]].siz),tr[rt1].r,rt2);
push_up(rt1);
}
else
{
rt1=rt;
rt2=New();
tr[rt2].r=tr[rt1].r;
tr[rt1].r=0;
k-=tr[tr[rt].l].siz;
int len=Seg::tr[tr[rt].rt[0]].siz;
if(tr[rt].tag) swap(tr[rt].rt[0],tr[rt].rt[1]);
//这里交换的目的是因为大小反过来了,反正是排完序了,我们两个分裂方式不想变
//那就交换一下
//最后再交换回来得到,不得不说,这个真的很妙
Seg::spilt(tr[rt].rt[0],-INF,INF,k,tr[rt1].rt[0],tr[rt2].rt[0]);
Seg::spilt(tr[rt].rt[1],-INF,INF,len-k,tr[rt2].rt[1],tr[rt1].rt[1]);
if(tr[rt].tag)
{
swap(tr[rt1].rt[0],tr[rt1].rt[1]);
swap(tr[rt2].rt[0],tr[rt2].rt[1]);
tr[rt2].tag=1;
}
push_up(rt1);
push_up(rt2);
}
}
int Merge(int rt1,int rt2)
{
if(rt1==0||rt2==0) return rt1+rt2;
pd(rt1); pd(rt2);
//反正平衡树维护的是区间,合并一下表示排序之后也是无影响的
if(tr[rt1].key>=tr[rt2].key)
{
tr[rt1].r=Merge(tr[rt1].r,rt2);
push_up(rt1);
return rt1;
}
else
{
tr[rt2].l=Merge(rt1,tr[rt2].l);
push_up(rt2);
return rt2;
}
}
//感觉很像一个珂朵莉啊
int Cover(int rt,int l,int r,int k)
{
int rt1,rt2,rt3;
spilt(rt,r,rt1,rt3);
spilt(rt1,l-1,rt1,rt2);
Del(rt2);
rt2=New(k,r-l+1);
return Merge(Merge(rt1,rt2),rt3);
}
int Reverse(int rt,int l,int r)
{
int rt1,rt2,rt3;
spilt(rt,r,rt1,rt3);
spilt(rt1,l-1,rt1,rt2);
Rev(rt2);
return Merge(Merge(rt1,rt2),rt3);
}
int Negat(int rt,int l,int r)
{
int rt1,rt2,rt3;
spilt(rt,r,rt1,rt3);
spilt(rt1,l-1,rt1,rt2);
Neg(rt2);
return Merge(Merge(rt1,rt2),rt3);
}
int Compress(int x)
{
if(x==0) return 0;
pd(x);
tr[x].l=Compress(tr[x].l);
tr[x].r=Compress(tr[x].r);
tr[x].rt[0]=Seg::Merge(tr[x].rt[0],Seg::Merge(tr[tr[x].l].rt[0],tr[tr[x].r].rt[0],-INF,INF),-INF,INF);
tr[x].rt[1]=Seg::Merge(tr[x].rt[1],Seg::Merge(tr[tr[x].l].rt[1],tr[tr[x].r].rt[1],-INF,INF),-INF,INF);
if(tr[x].l) rub.push_back(tr[x].l);
if(tr[x].r) rub.push_back(tr[x].r);
return x;
}
int Sort(int rt,int l,int r)
{
int rt1,rt2,rt3;
spilt(rt,r,rt1,rt3);
spilt(rt1,l-1,rt1,rt2);
rt2=Compress(rt2);
tr[rt2].l=tr[rt2].r=0;
tr[rt2].tag=0;
ll Sum=Seg::tr[tr[rt2].rt[0]].sum;
ll MAX=Seg::Get_Max(tr[rt2].rt[0],-INF,INF);
ll MIN=Seg::Get_Min(tr[rt2].rt[0],-INF,INF);
ll Mul=Seg::tr[tr[rt2].rt[0]].mul;
printf("%lld %lld %lld %lld \n",(Sum%mod+mod)%mod,(MAX%mod+mod)%mod,(MIN%mod+mod)%mod,(Mul%mod+mod)%mod);
return Merge(rt1,Merge(rt2,rt3));
}
}
int n,q,a[MAXN];
signed main()
{
scanf("%d%d",&n,&q);
using namespace FHQ;
Seg::Init(0);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
rt=Merge(rt,New(a[i],1));
}
for(int i=1,opt,l,r,k;i<=q;i++)
{
scanf("%d%d%d",&opt,&l,&r);
if(opt==1)
{
scanf("%d",&k);
rt=Cover(rt,l,r,k);
}
if(opt==2)
{
rt=Negat(rt,l,r);
}
if(opt==3)
{
rt=Reverse(rt,l,r);
}
if(opt==4)
{
rt=Sort(rt,l,r);
}
}
}
\(T3\)
一开始想的是枚举最大值计算有多少种情况,发现在有重复数字时候寄了
错误式子是
\(\large \frac{\sum_{max}max\times C(Num_{min},k-1)}{C(Num,k)}\)
很容易发现一件事,就是我们枚举了最大值可是并不是知道最大值是谁,因为这个是有标号的
那么我们假定同样大小编号大的比较大的话,就能保证不重复了
设\(a\)为降序排序序列,设\(b\)为升序排序序列
我们考虑答案计算
\(\sum_{i=l}^r C(r-i,k-1)\times a[i]\)
这样复杂度不是很优
考场上看到了\(\sum k<=100000,\)感觉\(k\)的种类不是很多,就想着全部预处理出来,没去想根号分治...
考虑进行根号分治
我们要计算区间\((l,r)\)
此时序列降序
我们提前预处理\(dp[i][j]\)表示\(k=i\)时,\(\sum_{z=1}^j C(j-z,k-1)\times b[z],\)也就是\(1-j\)区间选\(k\)个数字最大值的总和
我们现在要求\(\sum_{z=l}^r C(r-z,k-1)\times b[z]=dp[k][r]-\sum_{z=1}^{l-1}C(r-z,k-1)\times b[z]\)
考虑把他转化到\(dp[k][l-1]\)上
\(\sum_{z=1}^{l-1}C(r-z,k-1)\times b[z]=\sum_{j}dp[j][l-1]\times C(r-l+1,k-j)\)
组合意义比较显然,假设我们的贡献在前面,我们可以枚举前面选了多少个,然后后面任选的减去就好了,然后就\(hhh\)了
\(dp[i][j]=dp[i-1][j-1]+dp[i][j-1]\)
把式子拆开
\(\large \sum_{z=1}^j C(j-z,i-1)b[z]=\sum_{z=1}^{j-1} C((j-1)-z,i-2)b[z]+\sum_{z=1}^{j-1} C((j-1)-z,i-1)b[z]\)
较为显然,直接是组合数递推式的样子
#include<bits/stdc++.h>
#define int long long
#define mod 998244353
#define MAXN 100005
#define Lim 300
using namespace std;
int inv[MAXN],fac[MAXN],b[MAXN],a[MAXN],n,q;
int dp[Lim+5][MAXN];
inline int rd(){
int r = 0 , w = 1;
char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') w = -1; ch = getchar();}
while(ch >='0' && ch <='9') {r = r * 10 + ch -'0'; ch = getchar();}
return r * w;
}
void Init()
{
inv[0]=fac[0]=1;
inv[1]=fac[1]=1;
for(int i=2;i<MAXN;i++)
{
fac[i]=(fac[i-1]*i)%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
for(int i=1;i<MAXN;i++)
{
inv[i]=(inv[i-1]*inv[i])%mod;
}
}
int C(int n,int m)
{
if(m<0||m>n) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int my_pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
{
res=res*a%mod;
}
a=a*a%mod;
b>>=1;
}
return res;
}
signed main()
{
scanf("%lld%lld",&n,&q);
Init();
for(int i=1;i<=n;i++)
{
a[i]=rd();
b[i]=a[i];
}
sort(a+1,a+1+n);
sort(b+1,b+1+n);
reverse(a+1,a+1+n);
for(int i=1;i<=n;i++) dp[1][i]=(dp[1][i-1]+a[i])%mod;
for(int i=2;i<=Lim;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=(dp[i-1][j-1]+dp[i][j-1])%mod;
}
}
// for(int i=1;i<=n;i++)
// {
// cout<<b[i]<<" ";
// }
// cout<<"\n";
for(int i=1,l,r,k;i<=q;i++)
{
// cout<<"shu: "<<l<<" "<<r<<"\n";
l=rd();
r=rd();
k=rd();
l=lower_bound(b+1,b+1+n,l)-b;
r=upper_bound(b+1,b+1+n,r)-b-1;
// cout<<"Sit: "<<l<<" "<<r<<"\n";
printf("%lld ",r-l+1);
swap(l,r);
l=n-l+1;
r=n-r+1;
int Ans=0;
if(k==0)
{
puts("0");
continue;
}
if(k>r-l+1)
{
puts("-1");
continue;
}
if(k>Lim)
{
for(int j=l;j<=r;j++)
{
Ans=(Ans+C(r-j,k-1)*a[j])%mod;
}
}
else
{
Ans=dp[k][r]%mod;
for(int j=1;j<=k;j++)
{
Ans-=C(r-l+1,k-j)*dp[j][l-1]%mod;
}
Ans%=mod;
Ans+=mod;
Ans%=mod;
}
Ans=(Ans*my_pow(C(r-l+1,k),mod-2)%mod);
printf("%lld\n",Ans);
// cout<<Ans<<"\n";
}
return 0;
}
/*
7 3
83 74 100 89 95 79 72
90 100 3
80 89 1
70 79 2
*/
5.4 NOI模拟的更多相关文章
- 5.30 NOI 模拟
$5.30\ NOI $模拟 高三大哥最后一次模拟考了,祝他们好运 \(T1\)装箱游戏 显然可以将四种字母之间的空缺当做状态枚举 那么这道题就很显然了 #include<bits/stdc++ ...
- 5.23 NOI 模拟
$5.23\ NOI $模拟 \(T1\)简单的计算几何题 \(zjr:\)我当时没改,那么自己看题解吧 倒是有个简单的随机化方法(能获得\(72pts,\)正确性未知)\(:\) 随机两条切椭圆的平 ...
- 5.6 NOI模拟
\(5.6\ NOI\)模拟 明天就母亲节了,给家里打了个电话(\(lj\ hsez\)断我电话的电,在宿舍打不了,只能用教练手机打了) 其实我不是很能看到自己的\(future,\)甚至看不到高三的 ...
- NOI模拟赛 Day1
[考完试不想说话系列] 他们都会做呢QAQ 我毛线也不会呢QAQ 悲伤ING 考试问题: 1.感觉不是很清醒,有点困╯﹏╰ 2.为啥总不按照计划来!!! 3.脑洞在哪里 4.把模拟赛当作真正的比赛,紧 ...
- NOI 模拟赛 #2
得分非常惨惨,半个小时写的纯暴力 70 分竟然拿了 rank 1... 如果 OYJason 和 wxjor 在可能会被爆踩吧 嘤 T1 欧拉子图 给一个无向图,如果一个边集的导出子图是一个欧拉回路, ...
- 【2018.12.10】NOI模拟赛3
题目 WZJ题解 大概就是全场就我写不过 $FFT$ 系列吧……自闭 T1 奶一口,下次再写不出这种 $NTT$ 裸题题目我就艹了自己 -_-||| 而且这跟我口胡的自创模拟题 $set1$ 的 $T ...
- 6.28 NOI模拟赛 好题 状压dp 随机化
算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...
- NOI模拟赛Day5
T1 有and,xor,or三种操作,每个人手中一个数,求和左边进行某一种运算的最大值,当t==2时,还需要求最大值的个数. test1 20% n<=1000 O(n^2)暴力 test2 2 ...
- NOI模拟赛Day4
看到成绩的时候我的内心** woc第一题写错了呵呵呵呵呵呵呵呵 人不能太浪,会遭报应的** ------------------------------------------------------ ...
随机推荐
- 709. To Lower Case - LeetCode
Question 709. To Lower Case Sollution 题目大意:字符串大写转小写 思路: 直接调用Java API函数 字符串转char数组,遍历数组,判断如果大写就转小写 Ja ...
- java框架--快速入门
spring快速入门 1.创建项目 1.1创建项目文件夹 1.2启动idea ->文件->打开->点击创建的项目文件夹 1.3右键创建 ...
- 课堂测试——HDFS操作
要求: 编程实现以下功能,并利用Hadoop提供的Shell命令完成相同任务: 向HDFS中上传任意文本文件,如果指定的文件在HDFS中已经存在,则由用户来指定是追加到原有文件末尾还是覆盖原有的文件: ...
- Python数据分析--Numpy常用函数介绍(3)
摘要:先汇总相关股票价格,然后有选择地对其分类,再计算移动均线.布林线等. 一.汇总数据 汇总整个交易周中从周一到周五的所有数据(包括日期.开盘价.最高价.最低价.收盘价,成交量等),由于我们的数据是 ...
- antdVue问题
antdVue框架问题 #(1)slot/slot-scope插槽问题 一般用于表格数据渲染 eg: <span slot="user" slot-scope="t ...
- VS Code - Vim 插件自动切换输入法
前言: 在使用 Linux 的过程中,vim 是一个不错的编辑器,以至于多数人将其用成了习惯,在没有 vim 的环境下还是习惯用 vim 的快捷键来编辑文本.所以便有开发者们为众多的 IDE 和文本编 ...
- Linux查看日志文件写入速度的4种方法
原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 有时,我们需要查看某个文件的增长速度,如日志文件,以此来感受系统的负载情况,因为一般情况下,日志写入越快,说明系统 ...
- JAVA 异常 基本知识
异常 异常定义 异常是运行过程中出现的错误 人为错误:填写错误等 随机错误:网络中断.内存耗尽等 一个健壮的程序必须处理各种各样的错误 Java的异常是class Object Throwable E ...
- 将 Ubuntu 16.04 LTS 的 Unity 启动器移动到桌面底部命令
将 Ubuntu 16.04 LTS 的 Unity 启动器移动到桌面底部命令: gsettings set com.canonical.Unity.Launcher launcher-positio ...
- 领导:谁再用redis过期监听实现关闭订单,立马滚蛋!
日前拜读阿牛老师的大作 领导:谁再用定时任务实现关闭订单,立马滚蛋! 发现其方案有若干瑕疵,特此抛砖引玉讨论一二. 在电商.支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时 ...