【NOI2019十二省联合省选】部分题简要题解
Day 1 T1 异或粽子
题意:给出一个长为n的序列,选择K个不完全重合的区间使得每个区间的异或值的总和最大。
题解:先做一个前缀异或和,对于每一个右端点我们记录三元组(l,r,x)表示在左端点在\([l,r]\)内,最大异或值为x,塞进堆里。每次取出堆顶,并将该三元组对应的区间分裂成两个,重新扔回堆里。计算区间最大异或值利用可持久化字典树。
时间复杂度\(O(n\log n+K\log Maxvalue)\)
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 500005
#define M 17000005
#define L 33
#define LL long long
using namespace std;
LL a[N],cf[L],ans,d[N];
int t[M][2],rt[N],mw,mx[M];
int n,cnt,n1;
void ins(int k,int x,int w)
{
fod(i,mw,0)
{
int p=((a[w]&cf[i])>0);
t[k][p^1]=t[x][p^1];
mx[k]=w;
k=t[k][p]=++n1,x=t[x][p];
}
mx[k]=w;
}
void read(LL &x)
{
x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
}
struct node
{
int x,l,r,w;
LL v;
friend bool operator <(node x,node y)
{
return x.v<y.v;
}
};
priority_queue<node> h;
int get(int l,int r,LL v)
{
int k=rt[r];
fod(i,mw,0)
{
int p=(v&cf[i])>0;
k=(t[k][1-p]&&mx[t[k][1-p]]>=l)?t[k][1-p]:t[k][p];
}
return mx[k];
}
int main()
{
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
cin>>n>>cnt;
cf[0]=1;
fo(i,1,32) cf[i]=cf[i-1]*(LL)2;
rt[0]=++n1;
mw=0;
fo(i,1,n)
{
read(a[i]);
a[i]^=a[i-1];
fod(j,31,0) if(a[i]&cf[j]) {mw=max(mw,j);break;}
}
ins(1,0,0);
fo(i,1,n)
{
rt[i]=++n1;
ins(rt[i],rt[i-1],i);
int w=get(0,i-1,a[i]);
h.push((node){i,0,i-1,w,a[i]^a[w]});
}
fo(i,1,cnt)
{
node p=h.top();h.pop();
ans+=p.v;
int x=p.x;
if(p.l<p.w)
{
int w=get(p.l,p.w-1,a[x]);
h.push((node){x,p.l,p.w-1,w,a[x]^a[w]});
}
if(p.r>p.w)
{
int w=get(p.w+1,p.r,a[x]);
h.push((node){x,p.w+1,p.r,w,a[x]^a[w]});
}
}
printf("%lld\n",ans);
}
Day 1 T2 字符串问题
题意:给出一个字符串S,将S中的一些子串定为A串,一些定为B串,给出了m组某个A串支配某个B串的关系,要求选出一个最长的字符串T,T为A串拼接而成,且任意相邻的两个A串(ai,ai+1)中,存在一个B串,它是ai+1的前缀,且被ai支配。求最长的长度,需要判是否无限长。
题解:将倒着做一遍,将后缀自动机的fail树弄出来(后缀树),然后我们将包含A,B串的节点设为关键点,将A,B串在这个节点上对应的长度分裂成一个新节点,同一个节点分裂出来的点从长度小到大连边,显然这个新构出来的树点数还是是\(O(n)\)的,将支配关系也看做边连起来,最后新建一个节点,每个A串向这个点连边表示结束。跑拓扑序DP即可,如果出环就是无限长。
分裂节点、找节点的时候利用map和倍增来做,总的时间复杂度\(O(n\log n)\)
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 900005
#define M 2000005
#define LL long long
using namespace std;
int t[N][26],n1,mx[N],ft[N][20],n,m,ma,mb,fs[N],nt[M],dt[M],pr[M],m1,mq,pt[2][N],len[N],d[N],rd[N],n2;
LL f[N],ans;
char st[N];
struct node
{
int x,y,p,w;
friend bool operator <(node x,node y)
{
return (x.x<y.x)||(x.x==y.x&&x.y<y.y);
}
}ask[N];
map<int,int> h[N];
typedef map<int,int>::iterator IT;
void link(int x,int y,int z)
{
nt[++m1]=fs[x];
dt[fs[x]=m1]=y;
rd[y]++;
pr[m1]=z;
}
void make()
{
int ls=1;
n1=1;
fo(j,0,25) t[1][j]=0;
fod(i,n,1)
{
int c=st[i]-'a',p=ls;
mx[ls=++n1]=n-i+1;
fo(j,0,25) t[n1][j]=0;
while(p&&!t[p][c]) t[p][c]=ls,p=ft[p][0];
if(p)
{
int q=t[p][c];
if(mx[q]==mx[p]+1) ft[ls][0]=q;
else
{
ft[++n1][0]=ft[q][0];
mx[n1]=mx[p]+1;
ft[q][0]=ft[ls][0]=n1;
fo(j,0,25) t[n1][j]=t[q][j];
while(p&&t[p][c]==q) t[p][c]=n1,p=ft[p][0];
}
}
else ft[ls][0]=1;
}
}
void jump(int &x,int e)
{
for(int j=19;mx[ft[x][0]]>=e;)
{
while(j&&mx[ft[x][j]]<e) j--;
x=ft[x][j];
}
}
void read(int &x)
{
x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
}
void bfs()
{
d[0]=0;
fo(i,1,n2) if(!rd[i]) d[++d[0]]=i;
int l=0;
while(l<d[0])
{
int k=d[++l];
ans=max(ans,f[k]);
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
f[p]=max(f[p],f[k]+pr[i]);
rd[p]--;
if(rd[p]==0) d[++d[0]]=p;
}
}
}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
int t1;
cin>>t1;
while(t1--)
{
fo(i,1,n1)
{
mx[i]=0,h[i].clear();
ft[i][0]=0;
}
fo(i,1,n2) fs[i]=rd[i]=f[i]=0;
m1=0;
n1=n2=0;
mq=0;
scanf("\n%s",st+1);
n=strlen(st+1);
make();
fo(i,1,n1) h[i][mx[i]]=i;
read(ma);
fo(i,1,ma)
{
int l,r;
read(l),read(r);
ask[++mq]=(node){l,r,0,i};
len[i]=r-l+1;
}
read(mb);
fo(i,1,mb)
{
int l,r;
read(l),read(r);
ask[++mq]=(node){l,r,1,i};
}
sort(ask+1,ask+mq+1);
fo(j,1,19)
fo(i,1,n1) ft[i][j]=ft[ft[i][j-1]][j-1];
int k=1,j=n,w=1;
n2=n1;
ask[mq+1].x=n+1;
fod(i,mq,1)
{
if(i==mq||ask[i].x!=ask[i+1].x)
{
fod(p,ask[i+1].x-1,ask[i].x) k=t[k][st[p]-'a'];
w=k;
}
jump(w,ask[i].y-ask[i].x+1);
if(!h[w][ask[i].y-ask[i].x+1]) h[w][ask[i].y-ask[i].x+1]=++n2;
pt[ask[i].p][ask[i].w]=h[w][ask[i].y-ask[i].x+1];
}
fo(i,1,n1)
{
IT it=h[i].begin();
int ls=ft[i][0];
for(;it!=h[i].end();it++)
if(ls) link(ls,(*it).second,0),ls=(*it).second;
}
read(m);
fo(i,1,m)
{
int x,y;
read(x),read(y);
link(pt[0][x],pt[1][y],len[x]);
}
n2++;
ans=0;
fo(i,1,ma) link(pt[0][i],n2,len[i]);
bfs();
if(d[0]!=n2) printf("-1\n");
else printf("%lld\n",ans);
}
}
Day 2 T1 皮配
题意:有n所学校,c座城市,每个学校都属于某一个城市。现在有4位导师,分成两大阵营(1,2),(3,4)和两大派系(1,3),(2,4)。每个阵营和每个派系都有一个人数上限,每个学校有一个选手人数。现在问有多少种分配方案,满足同一个城市的学校选择同一个阵营,同一个学校的所有选手选择同一个导师。此外,有k所学校的选手一定不会选择某一位导师。
题解:若k=0,我们容易发现阵营限制和派系限制是独立的,由于一个城市必须选择相同阵营,且对于学校来说所属城市选哪个阵营没有关系。我们只需要将城市对于阵营人数做背包,学校对于派系做背包,最后答案乘在一起即可。
若k!=0,我们将有限制的k个学校取出,剩余没有限制的学校和城市跟之前一样DP。剩下k所学校暴力做同时两维限制的背包即可,最后再将三个东西乘在一起。
直接做容易卡常数,我们发现每所学校的人数不超过10,那么DP时和的上界对总人数取个min,单组数据复杂度就优化到是\(O((n+c)M+kM*10k)\)
相当丑...
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 2505
#define LL long long
#define mo 998244353
#define L 12
using namespace std;
LL f[N][N],g[N][N],cf[L+1],l2[L+1],h[2][N][N][2];
int a[N],c[N],s[N],t,n,m,lim[4],mx,bp[N],fr[N],n1,m1,d[N],d2[N],bq[2][N][N][2];
bool bz[N];
bool cmp(int x,int y)
{
return (bp[x]<bp[y]);
}
bool cmp2(int x,int y)
{
return (bz[x]<bz[y]);
}
void inc(LL &x,LL v)
{
x=(x+v)%mo;
}
bool cmp3(int x,int y)
{
return fr[x]<fr[y];
}
LL gf(int l,int r)
{
if(r>lim[2]) r=lim[2];
if(r<0) return 0;
LL s=f[n1][r];
if(l>0) s=(s-f[n1][l-1]+mo)%mo;
return s;
}
LL gg(int l,int r)
{
if(r>lim[0]) r=lim[0];
if(r<0||l>r) return 0;
LL s=g[m1][r];
if(l>0) s=(s-g[m1][l-1]+mo)%mo;
return s;
}
int main()
{
freopen("mentor.in","r",stdin);
freopen("mentor.out","w",stdout);
cin>>t;
cf[0]=1;
fo(i,1,L) l2[cf[i]=cf[i-1]<<1]=i;
while(t--)
{
mx=0;
memset(bz,0,sizeof(bz));
memset(s,0,sizeof(s));
memset(f,0,sizeof(f));
memset(h,0,sizeof(h));
memset(g,0,sizeof(g));
memset(bq,0,sizeof(bq));
memset(bp,255,sizeof(bp));
scanf("%d%d",&n,&m);
int sum=0;
fo(j,0,3) scanf("%d",&lim[j]),mx=max(mx,lim[j]);
fo(i,1,n)
{
int x,y;
scanf("%d%d",&x,&y);
fr[i]=x;
sum+=y;
s[x]+=y,c[i]=y;
d[i]=i;
}
int lc;
scanf("%d",&lc);
fo(i,1,lc)
{
int x,y;
scanf("%d%d",&x,&y);
bp[x]=y;
bz[fr[x]]=1;
}
sort(d+1,d+n+1,cmp);
n1=0;
while(n1<n&&bp[d[n1+1]]<0) n1++;
f[0][0]=1;
fo(i,1,n1)
{
fo(j,0,lim[2])
{
f[i][j]=f[i-1][j];
if(j>=c[d[i]]) inc(f[i][j],f[i-1][j-c[d[i]]]);
}
}
fo(i,1,m) d2[i]=i;
sort(d2+1,d2+m+1,cmp2);
m1=0;
while(m1<m&&!bz[d2[m1+1]]) m1++;
g[0][0]=1;
fo(i,1,m1)
{
fo(j,0,lim[0])
{
g[i][j]=g[i-1][j];
if(s[d2[i]]>0&&j>=s[d2[i]]) inc(g[i][j],g[i-1][j-s[d2[i]]]);
}
}
h[0][0][0][0]=1,bq[0][0][0][0]=n1;
sort(d+n1+1,d+n+1,cmp3);
int vr=0;
fo(i,n1+1,n)
{
int i1=(i-n1)&1;
if(i==n1+1||fr[d[i]]!=fr[d[i-1]]) vr+=s[fr[d[i]]];
fo(p,0,1)
{
int r1=min(lim[0],vr);
fo(j,0,r1)
{
int uj=(p==0&&(i==n1+1||fr[d[i]]!=fr[d[i-1]]))?j-s[fr[d[i]]]:j;
if(uj>=0)
{
int r2=min(10*(i-n1),lim[2]);
fo(k,0,r2)
{
h[i1][j][k][p]=0;
bq[i1][j][k][p]=i;
if(!(i==n1+1||fr[d[i]]!=fr[d[i-1]]))
{
if(bp[d[i]]!=p*2&&k>=c[d[i]])
{
if(bq[1^i1][uj][k-c[d[i]]][p]==i-1) inc(h[i1][j][k][p],h[1^i1][uj][k-c[d[i]]][p]);
}
if(bp[d[i]]!=p*2+1)
{
if(bq[1^i1][uj][k][p]==i-1) inc(h[i1][j][k][p],h[1^i1][uj][k][p]);
}
}
else
{
if(bp[d[i]]!=p*2&&k>=c[d[i]])
{
if(bq[1^i1][uj][k-c[d[i]]][0]==i-1) inc(h[i1][j][k][p],h[1^i1][uj][k-c[d[i]]][0]);
if(bq[1^i1][uj][k-c[d[i]]][1]==i-1) inc(h[i1][j][k][p],h[1^i1][uj][k-c[d[i]]][1]);
}
if(bp[d[i]]!=p*2+1)
{
if(bq[1^i1][uj][k][0]==i-1) inc(h[i1][j][k][p],h[1^i1][uj][k][0]);
if(bq[1^i1][uj][k][1]==i-1) inc(h[i1][j][k][p],h[1^i1][uj][k][1]);
}
}
}
}
}
}
}
fo(i,1,lim[2]) inc(f[n1][i],f[n1][i-1]);
fo(i,1,lim[0]) inc(g[m1][i],g[m1][i-1]);
int i1=(n-n1)&1;
LL ans=0;
fo(j,0,lim[0]) fo(k,0,lim[2])
{
if(bq[i1][j][k][0]==n) inc(ans,h[i1][j][k][0]*gg(sum-j-lim[1],lim[0]-j)%mo*gf(sum-k-lim[3],lim[2]-k)%mo);
if(bq[i1][j][k][1]==n) inc(ans,h[i1][j][k][1]*gg(sum-j-lim[1],lim[0]-j)%mo*gf(sum-k-lim[3],lim[2]-k)%mo);
}
printf("%lld\n",ans);
}
}
Day 2 T2 春节十二响
题意:给出一棵有根树,每个节点有点权,要将所有节点放入若干个集合中,每个集合的代价是集合中点权最大值。要求树上祖先和后代不能放入同一个集合。求最小总代价。
题解:先考虑一条链的情况(根不一定为链端),那一定是长一点的链上每个节点给一个集合,短的链的点塞进这些集合,根节点自成一个集合。深入思考可以发现一定是大的和大的点放在一起,小的和小的放在一起更优。同时这启发我们集合个数与深度有关。
扩展到树上,我们采用长链剖分,对于每条长链维护一个堆记录集合的权值,合并子树的时候就暴力取出长链的前若干大合并,再暴力塞回去。
由长链剖分的时间复杂度分析,这样做的时间复杂度是\(O(n\log n)\)的。
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 200005
#define LL long long
using namespace std;
int fs[N],nt[2*N],dt[2*N],ft[N],n,m,pr[N],dep[N],son[N],m1;
LL ans;
priority_queue<int> h[N];
int rt[N],n1,u1[N];
void link(int x,int y)
{
nt[++m1]=fs[x];
dt[fs[x]=m1]=y;
}
void make(int k,int fa)
{
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(p!=fa) make(p,k),son[k]=(dep[p]>dep[son[k]])?p:son[k];
}
dep[k]=dep[son[k]]+1;
}
void dfs(int k,int fa)
{
if(son[k]) dfs(son[k],k),rt[k]=rt[son[k]];
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(p!=fa&&p!=son[k]) dfs(p,k);
}
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(p!=fa&&p!=son[k])
{
u1[0]=0;
while(!h[rt[p]].empty())
{
int x=h[rt[k]].top(),y=h[rt[p]].top();
h[rt[k]].pop(),h[rt[p]].pop();
u1[++u1[0]]=max(x,y);
}
fo(j,1,u1[0]) h[rt[k]].push(u1[j]);
}
}
if(!rt[k]) rt[k]=++n1;
h[rt[k]].push(pr[k]);
}
int main()
{
freopen("spring.in","r",stdin);
freopen("spring.out","w",stdout);
cin>>n;
fo(i,1,n) scanf("%d",&pr[i]);
fo(i,2,n) scanf("%d",&ft[i]),link(ft[i],i);
make(1,0);
dfs(1,0);
ans=0;
while(!h[rt[1]].empty())
{
ans+=h[rt[1]].top();
h[rt[1]].pop();
}
printf("%lld\n",ans);
}