\(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模拟的更多相关文章

  1. 5.30 NOI 模拟

    $5.30\ NOI $模拟 高三大哥最后一次模拟考了,祝他们好运 \(T1\)装箱游戏 显然可以将四种字母之间的空缺当做状态枚举 那么这道题就很显然了 #include<bits/stdc++ ...

  2. 5.23 NOI 模拟

    $5.23\ NOI $模拟 \(T1\)简单的计算几何题 \(zjr:\)我当时没改,那么自己看题解吧 倒是有个简单的随机化方法(能获得\(72pts,\)正确性未知)\(:\) 随机两条切椭圆的平 ...

  3. 5.6 NOI模拟

    \(5.6\ NOI\)模拟 明天就母亲节了,给家里打了个电话(\(lj\ hsez\)断我电话的电,在宿舍打不了,只能用教练手机打了) 其实我不是很能看到自己的\(future,\)甚至看不到高三的 ...

  4. NOI模拟赛 Day1

    [考完试不想说话系列] 他们都会做呢QAQ 我毛线也不会呢QAQ 悲伤ING 考试问题: 1.感觉不是很清醒,有点困╯﹏╰ 2.为啥总不按照计划来!!! 3.脑洞在哪里 4.把模拟赛当作真正的比赛,紧 ...

  5. NOI 模拟赛 #2

    得分非常惨惨,半个小时写的纯暴力 70 分竟然拿了 rank 1... 如果 OYJason 和 wxjor 在可能会被爆踩吧 嘤 T1 欧拉子图 给一个无向图,如果一个边集的导出子图是一个欧拉回路, ...

  6. 【2018.12.10】NOI模拟赛3

    题目 WZJ题解 大概就是全场就我写不过 $FFT$ 系列吧……自闭 T1 奶一口,下次再写不出这种 $NTT$ 裸题题目我就艹了自己 -_-||| 而且这跟我口胡的自创模拟题 $set1$ 的 $T ...

  7. 6.28 NOI模拟赛 好题 状压dp 随机化

    算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...

  8. NOI模拟赛Day5

    T1 有and,xor,or三种操作,每个人手中一个数,求和左边进行某一种运算的最大值,当t==2时,还需要求最大值的个数. test1 20% n<=1000 O(n^2)暴力 test2 2 ...

  9. NOI模拟赛Day4

    看到成绩的时候我的内心** woc第一题写错了呵呵呵呵呵呵呵呵 人不能太浪,会遭报应的** ------------------------------------------------------ ...

随机推荐

  1. cut-列过滤

    列过滤命令. 语法 cut [选项] 要过滤的字符串 选项 -f 以字段为单位进行分割 -c 以字符为单位进行分割 -b 以字节为单位进行分割 -d 以分割符为单位进行分割,分隔符可以是"冒 ...

  2. split(),strip,split("/")[-1] 和 split("/",-1)的区别

    Python中split()函数,通常用于将字符串切片并转换为列表. 一.函数说明: split():语法: str.split(str="",num=string.count(s ...

  3. FlinkSQL 之乱序问题

    乱序问题 在业务编写 FlinkSQL 时, 非常常见的就是乱序相关问题, 在出现问题时,非常难以排查,且无法稳定复现,这样无论是业务方,还是平台方,都处于一种非常尴尬的地步. 在实时 join 中, ...

  4. 一个ES设置操作引发的“血案”

    背景说明 ES版本 7.1.4 在ES生产环境中增加字段,一直提示Setting index.mapper.dynamic was removed after version 6.0.0错误.但是我只 ...

  5. Spring框架系列(5) - 深入浅出SpringMVC请求流程和案例

    前文我们介绍了Spring框架和Spring框架中最为重要的两个技术点(IOC和AOP),那我们如何更好的构建上层的应用呢(比如web 应用),这便是SpringMVC:Spring MVC是Spri ...

  6. 『现学现忘』Docker基础 — 39、实战:自定义Tomcat9镜像

    目录 1.目标 2.准备 3.编写Dockerfile文件 4.构建镜像 5.启动镜像 6.验证容器是否能够访问 7.向容器中部署WEB项目,同时验证数据卷挂载 (1)准备一个简单的WEB项目 (2) ...

  7. python小题目练习(一)

    题目:输出1+2+3+4+5+--+100的总数,并打印出这行式子 代码展示:# 1.定义一个初识变量total,用于后面每次循环进行累加值 total = 0# 2.利用for循环遍历累加for i ...

  8. 12.1 Android Studio如何手动下载Gradle文件

    实际操作过程中,可能由于各方面原因,导致Gradle无法下载,或者下载比较慢,这个时候,其实我们可以手动下载,或者找一个最近的版本,替换他. 确认要下载的版本 不论是用命令编译Android项目,还是 ...

  9. Pod控制器类型

    Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类: - 自主式pod:kubernetes直接创建出来的Pod,这种pod删除后就没有了,也不 ...

  10. 从0到1建设智能灰度数据体系:以vivo游戏中心为例

    作者: vivo 互联网数据分析团队-Dong Chenwei vivo 互联网大数据团队-Qin Cancan.Zeng Kun 本文介绍了vivo游戏中心在灰度数据分析体系上的实践经验,从&quo ...