因为耽误了网络同步赛,所以在奖金一个月后进行了NOI的测试。
DAY1
T1程序自动分析
题目大意:给定一些变量相等或不等的关系,判断是否矛盾。
思路:离散化后,并查集维护一下。水水的开始。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 200005 using namespace std; struct use{ int x1,x2,kk; }ask[maxnode]={0}; int ai[maxnode]={0},fa[maxnode]={0}; int cmp(const use &x,const use &y){return x.kk>y.kk;} int root(int x) { if (fa[x]!=x) fa[x]=root(fa[x]); return fa[x]; } int main() { freopen("prog.in","r",stdin); freopen("prog.out","w",stdout); int t,i,j,n,m,siz,r1,r2; bool f; scanf("%d",&t); while(t) { scanf("%d",&n);ai[0]=0; for (i=1;i<=n;++i) { scanf("%d%d%d",&ask[i].x1,&ask[i].x2,&ask[i].kk); ai[++ai[0]]=ask[i].x1; ai[++ai[0]]=ask[i].x2; } sort(ai+1,ai+ai[0]+1);f=false; siz=unique(ai+1,ai+ai[0]+1)-ai-1; sort(ask+1,ask+n+1,cmp); for (i=1;i<=2*n;++i) fa[i]=i; for (i=1;i<=n;++i) { ask[i].x1=upper_bound(ai+1,ai+siz+1,ask[i].x1)-ai-1; ask[i].x2=upper_bound(ai+1,ai+siz+1,ask[i].x2)-ai-1; r1=root(ask[i].x1);r2=root(ask[i].x2); if (ask[i].kk==1) { if (r1!=r2) { if (i%3==0) swap(r1,r2); fa[r1]=r2; } } else { if (r1==r2) { f=true;break; } } } if (f) printf("NO\n"); else printf("YES\n"); --t; } fclose(stdin); fclose(stdout); }
T2软件包管理器
题目大意:给定一些软件之间的依赖关系(一棵树结构),安装时要把它到根上的都安装,卸载时要把它子树里的都卸载,求每一个操作改变的软件个数。
思路:树链剖分——链和子树,线段树维护。水水的延续。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 using namespace std; char ch[10]; int tot=0,point[maxnode]={0},next[maxnode*2]={0},en[maxnode*2]={0},t[maxnode*4]={0}, ri[maxnode]={0},delta[maxnode*4]; bool visit[maxnode]={false}; void add(int u,int v) { ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v; ++tot;next[tot]=point[v];point[v]=tot;en[tot]=u; } void updata(int i){t[i]=t[i*2]+t[i*2+1];} void pushdown(int i,int l,int r) { int mid; mid=(l+r)/2; if (delta[i]>=0) { delta[i*2]=delta[i]; t[i*2]=(delta[i*2]==1 ? (mid-l+1) : 0); delta[i*2+1]=delta[i]; t[i*2+1]=(delta[i*2+1]==1 ? (r-mid) : 0); delta[i]=delta[0]; } } int task(int i,int l,int r,int ll,int rr,int kk) { int mid,ans=0; if (ll<=l&&r<=rr) return (kk==1 ? t[i] : r-l+1-t[i]); pushdown(i,l,r);mid=(l+r)/2; if (ll<=mid) ans+=task(i*2,l,mid,ll,rr,kk); if (rr>mid) ans+=task(i*2+1,mid+1,r,ll,rr,kk); return ans; } void tch(int i,int l,int r,int ll,int rr,int kk) { int mid; if (ll<=l&&r<=rr) { delta[i]=kk;t[i]=(kk==1 ? (r-l+1) : 0);return; } mid=(l+r)/2;pushdown(i,l,r); if (ll<=mid) tch(i*2,l,mid,ll,rr,kk); if (rr>mid) tch(i*2+1,mid+1,r,ll,rr,kk); updata(i); } struct lp{ int fa[maxnode],dep[maxnode],son[maxnode],siz[maxnode],tid[maxnode],top[maxnode]; void dfs1(int u,int f,int depth) { int i,j,maxsiz=0; visit[u]=true;fa[u]=f;dep[u]=depth; siz[u]=1;son[u]=0; for (i=point[u];i;i=next[i]) { if (!visit[j=en[i]]) { dfs1(j,u,depth+1); siz[u]+=siz[j]; if (siz[j]>maxsiz) { maxsiz=siz[j]; son[u]=j; } } } } void dfs2(int u,int anc) { int i,j; visit[u]=false;tid[u]=++tot;top[u]=anc; if (son[u]) dfs2(son[u],anc); for (i=point[u];i;i=next[i]) if (visit[j=en[i]]) dfs2(j,j); ri[u]=tot; } int ins(int a,int b) { int ans=0; while(top[a]!=top[b]) { if (dep[top[a]]<dep[top[b]]) swap(a,b); ans+=task(1,1,tot,tid[top[a]],tid[a],0); tch(1,1,tot,tid[top[a]],tid[a],1); a=fa[top[a]]; } if (dep[a]>dep[b]) swap(a,b); ans+=task(1,1,tot,tid[a],tid[b],0); tch(1,1,tot,tid[a],tid[b],1); return ans; } int uni(int a) { int ans=0; ans=task(1,1,tot,tid[a],ri[a],1); tch(1,1,tot,tid[a],ri[a],0); return ans; } }tree; int main() { freopen("manager.in","r",stdin); freopen("manager.out","w",stdout); int n,q,i,j; scanf("%d",&n); for (i=2;i<=n;++i) { scanf("%d",&j);add(++j,i); }tot=0; tree.dfs1(1,0,1);tree.dfs2(1,1); memset(delta,128,sizeof(delta)); scanf("%d",&q); for (i=1;i<=q;++i) { scanf("%*c%s%d",&ch,&j); if (ch[0]==‘i‘) { printf("%d\n",tree.ins(1,++j)); } else { printf("%d\n",tree.uni(++j)); } } fclose(stdin); fclose(stdout); }
T3寿司晚宴
题目大意:给定n-1种寿司,每个寿司编号2~n,求两个人去寿司的和谐方案的方案总数(一个方案称为和谐的当且仅当两人选择的寿司种类中两两编号互质。)
思路:测试的时候,只会暴力打表,但是还因为常数赋值时没写LL就挂了。后来问了sunshine大爷才知道正解。首先我们知道一个数n最多只有一个大于根号n的质因子,所以我们可以对一个数构成两个特征值:第1个表示大于根n的质因子,第2个表示小于根n的质因数组成(用8位2进制数表示,根号500以内的质数只有8个)。对第一个特征值排序后对这个大质因数相同的一类数一起处理(如果这个特征值为1,则一个一个的处理)。设f[i][j]表示第一个人选i,第二个人选j(i、j表示质因数的选择情况),g[k][i][j]表示第k+1个人第一个人选i第二个人选j的方案数,在每一类数做之前都用f给g数组赋值,做完这一类后f[i][j]=g[0][i][j]+g[1][i][j]-f[i][j](这里减掉的是两个都没选的情况),这里的循环要倒着来做,有点像分组背包,保证的是同一类数不可能同时出现在两边。
这道题目中处理质因数的方法很巧妙。虽然做题的时候想到了最多只有一个大于根n的质因数,但是没有灵活的用上。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1<<8 #define LL long long using namespace std; struct use{ int fi,se; }num[505]={0}; int n; LL f[maxnode][maxnode]={0},g[2][maxnode][maxnode]={0},prime[9]={0,2,3,5,7,11,13,17,19}; int cmp(const use &x,const use &y){return x.fi<y.fi;} int next(int i) { if (num[i].fi==1) return i; while(num[i].fi==num[i+1].fi&&i<n) ++i; return i; } int main() { freopen("dinner.in","r",stdin); freopen("dinner.out","w",stdout); int i,j,k,t,tt;LL p,ans=0; scanf("%d%lld",&n,&p); for (i=2;i<=n;++i) { k=i; for (j=1;j<=8;++j) { if (k%prime[j]==0) { num[i].se|=1<<(j-1); while(k%prime[j]==0) k/=prime[j]; } } num[i].fi=k; } sort(num+2,num+n+1,cmp); f[0][0]=1; for (i=2;i<=n;i=tt+1) { for (j=0;j<=255;++j) for (k=0;k<=255;++k) g[0][j][k]=g[1][j][k]=f[j][k]; tt=next(i); for (t=i;t<=tt;++t) for (j=255;j>=0;--j) for (k=255;k>=0;--k) { if ((j&num[t].se)==0) g[1][j][k|num[t].se]=(g[1][j][k|num[t].se]+g[1][j][k])%p; if ((k&num[t].se)==0) g[0][j|num[t].se][k]=(g[0][j|num[t].se][k]+g[0][j][k])%p; } for (j=0;j<=255;++j) for (k=0;k<=255;++k) f[j][k]=((g[0][j][k]+g[1][j][k]-f[j][k])%p+p)%p; } for (i=0;i<=255;++i) for (j=0;j<=255;++j) ans=(ans+f[i][j])%p; printf("%lld\n",ans); fclose(stdin); fclose(stdout); }
DAY2
T1荷马史诗
题目大意:给n个字符安排一个k进制的替代码,要求一个都不是其他的前缀,同时要求最长的替代码最小。
思路:可以转化成k叉哈夫曼树,要求树高尽量小。那么像合并果子那样,用一个优先队列维护,在权值相同的时候,先合并高度小的,最后输出就行了。虽然之前没见过,但在测试时根据样例和贪心,还是可以写出来的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define LL long long using namespace std; struct use{ LL val,dep; bool operator <(const use &x)const { return val==x.val ? dep>x.dep : val>x.val; } }; priority_queue<use> que; int main() { freopen("epic.in","r",stdin); freopen("epic.out","w",stdout); int n,k,i,j; LL x,ans=0,sum,ll; use y; scanf("%d%d",&n,&k); for (i=1;i<=n;++i) { scanf("%I64d",&x); que.push((use){x,0}); } j=n; if ((n-1)%(k-1)>0) j+=k-1-(n-1)%(k-1); for (i=n+1;i<=j;++i) que.push((use){0,0}); while(j>1) { sum=ll=0; for (i=1;i<=k;++i) { y=que.top();que.pop(); ans+=y.val;sum+=y.val; ll=max(ll,y.dep); } j-=k-1;que.push((use){sum,ll+1}); } y=que.top(); printf("%I64d\n%I64d\n",ans,y.dep); fclose(stdin); fclose(stdout); }
T2品酒大会
题目大意:给定一个字符串,求lcp(i,j)>=l(l=0~n-1)的对数和val[i]*val[j]的最大值。
思路:后缀数组的题目。跟差异很像,处理出sa,rank,height数组后分治处理一下就可以了。
测试时其他有同学按height从大到小排序,然后用并查集做,很神的做法啊。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<ctime> #define maxnode 300005 #define inf 0x7fffffffffffffffLL #define LL long long using namespace std; struct use{ int minn,minp; }tree[maxnode*4]={0}; struct uu{ LL maxn,minn; }sta; int sa[maxnode]={0},rank[maxnode]={0},c[maxnode]={0},t1[maxnode]={0},t2[maxnode]={0}, height[maxnode]={0},n,m; char ss[maxnode]; LL ans[2][maxnode]={0},val[maxnode]={0}; bool cmp(int *y,int a,int b,int k) { int a2,b2; a2= a+k>=n ? -1 : y[a+k]; b2= b+k>=n ? -1 : y[b+k]; a=y[a];b=y[b]; return a==b&&a2==b2; } void build() { int i,k,p,*x=t1,*y=t2; for (i=0;i<m;++i) c[i]=0; for (i=0;i<n;++i) ++c[x[i]=(ss[i]-‘a‘)]; for (i=1;i<m;++i) c[i]+=c[i-1]; for (i=n-1;i>=0;--i) sa[--c[x[i]]]=i; for (k=1;k<=n;k<<=1) { p=0; for (i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) c[i]=0; for (i=0;i<n;++i) ++c[x[y[i]]]; for (i=1;i<m;++i) c[i]+=c[i-1]; for (i=n-1;i>=0;--i) sa[--c[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++; if (m>=n) break; } } void pre() { int i,j,k=0; for (i=0;i<n;++i) rank[sa[i]]=i; for (i=0;i<n;++i) { if (!rank[i]) continue; if (k) --k; j=sa[rank[i]-1]; while(ss[i+k]==ss[j+k]) ++k; height[rank[i]]=k; } } use updata(use x1,use x2) { if (x1.minn<=x2.minn) return x1; else return x2; } uu updata2(uu x1,uu x2) { uu x3; x3.minn=min(x1.minn,x2.minn); x3.maxn=max(x1.maxn,x2.maxn); return x3; } void buildt(int i,int l,int r) { int mid; if (l==r) { tree[i].minn=height[l];tree[i].minp=l;return; } mid=(l+r)/2; buildt(i*2,l,mid);buildt(i*2+1,mid+1,r); tree[i]=updata(tree[i*2],tree[i*2+1]); } use task(int i,int l,int r,int ll,int rr) { int mid; use x1,x2; if (ll<=l&&r<=rr) return tree[i]; x1.minn=x2.minn=x1.minp=x2.minp=2100000000LL; mid=(l+r)/2; if (ll<=mid) x1=task(i*2,l,mid,ll,rr); if (rr>mid) x2=task(i*2+1,mid+1,r,ll,rr); return updata(x1,x2); } uu work(int l,int r) { if (l>=r) { if (l==r) return (uu){val[sa[l]],val[sa[l]]}; else return sta; } use x; x=task(1,0,n-1,l+1,r); ans[0][x.minn]+=(LL)(x.minp-l)*(LL)(r-x.minp+1); uu x1,x2; x1=work(l,x.minp-1);x2=work(x.minp,r); ans[1][x.minn]=max(ans[1][x.minn],max(x1.maxn*x2.maxn,max(x1.minn*x2.minn, max(x1.maxn*x2.minn,x1.minn*x2.maxn)))); return updata2(x1,x2); } int main() { freopen("savour.in","r",stdin); freopen("savour.out","w",stdout); int i,j; scanf("%d",&n);m=26; while(1) { ss[0]=getchar(); if (ss[0]>=‘a‘&&ss[0]<=‘z‘) break; } for (i=1;i<n;++i) ss[i]=getchar(); for (i=0;i<n;++i) scanf("%I64d",&val[i]); build();pre();buildt(1,0,n-1); memset(ans[1],128,sizeof(ans[1])); sta.maxn=ans[1][maxnode-1];sta.minn=inf;work(0,n-1); for (i=n-1;i>=0;--i) { ans[0][i]+=ans[0][i+1]; ans[1][i]=max(ans[1][i],ans[1][i+1]); } for (i=0;i<n;++i) printf("%I64d %I64d\n",ans[0][i],(ans[0][i]==0 ? 0 : ans[1][i])); fclose(stdin); fclose(stdout); }