NOI2015 题解
度过了神奇的学考时间,又回到了OI战场上了...
先刷了刷NOI2015,感觉好像不是很难的样子。
Day1
T1 程序自动分析
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4195
唔,给你相同的条件和不相同的条件,然后要你判断是不是有矛盾...好像只要先把所有的相同的用某个神奇的数据结构弄到一起,然后看一下要求不同的里面有没有在同一个联通快里的就行了...
这个神奇的数据结构,不就是并查集么?...
然后由于给的数字有点大,就需要离散化一下再并查集存起来...
好良心的送分题
//BZOJ 4195
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000010;
inline int in(){
int x=0;char ch=getchar();
while(ch>'9' || ch<'0') ch=getchar();
while(ch<='9' && ch>='0') x=x*10+ch-'0',ch=getchar();
return x;
}
int n,sz,tp;
int tmp[maxn<<1],Hash[maxn<<1];
int p[maxn<<1];
struct Node{
int x,y;
bool tp;
}s[maxn];
int Find_Hash(int x){
int l=1,r=tp,mid;
while(r-l>1){
mid=(l+r)>>1;
if(Hash[mid]>x) r=mid;
else if(Hash[mid]<x) l=mid;
else if(Hash[mid]==x) return mid;
}
if(Hash[l]==x) return l;
return r;
}
bool cmp(const Node &A,const Node &B){
return A.tp>B.tp;
}
inline int find(int x){
int r=x,pre;
while(p[r]!=r) r=p[r];
while(x!=r)
pre=p[x],p[x]=r,x=pre;
return r;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("prog.in","r",stdin);
freopen("prog.out","w",stdout);
#endif
int Kase=in();
int fx,fy;
while(Kase--){
n=in();
sz=0;
for(int i=1;i<=n;i++){
s[i].x=in(),s[i].y=in(),s[i].tp=in();
tmp[++sz]=s[i].x,tmp[++sz]=s[i].y;
}
sort(tmp+1,tmp+sz+1);
Hash[tp=1]=tmp[1];
for(int i=2;i<=sz;i++)
if(tmp[i]!=tmp[i-1]) Hash[++tp]=tmp[i];
sort(s+1,s+n+1,cmp);
for(int i=1;i<=tp;i++) p[i]=i;
int rec=0,fool=0;
for(int i=1;s[i].tp==1 && i<=n;i++){
fx=find(Find_Hash(s[i].x)),fy=find(Find_Hash(s[i].y));
p[fx]=fy;
rec=i;
}
for(int i=rec+1;i<=n;i++){
fx=find(Find_Hash(s[i].x)),fy=find(Find_Hash(s[i].y));
if(fx==fy) {fool=1;puts("NO");break;}
}
if(!fool) puts("YES");
}
return 0;
}
T2 软件包管理器
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4196
给你一颗树,刚开始所有节点都是0状态,有两种操作:
操作1:把点x到根的所有点全部改成1,问需要改动几个
操作2:把点x的子树全部改成0,问需要改动几个
唔,一个问一条链,一个问一棵子树,而且都是统一赋值,感觉就差不告诉你要打树链剖分了...然后改动几个也就是问一下改动前后的树上值的总和的变化...
好良心的模板题
//BZOJ 4196
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
inline int in(){
int x=0;char ch=getchar();
while(ch>'9' || ch<'0') ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
struct Node{
int data,next;
}node[maxn];
#define now node[point].data
#define then node[point].next
struct Tree{
int sm,pt;
Tree(){pt=-1;}
}s[maxn*17];
int n,cnt,Idex;
int head[maxn],Sz[maxn],Son[maxn],fa[maxn];
int id[maxn],el[maxn],hd[maxn];
void addedge(int u,int v){
node[cnt].data=v,node[cnt].next=head[u],head[u]=cnt++;
}
void dfs1(int x){
Sz[x]=1;Son[x]=-1;
for(int point=head[x];point!=-1;point=then){
dfs1(now);Sz[x]+=Sz[now];
if(Son[x]<0 || Sz[now]>Sz[Son[x]]) Son[x]=now;
}
}
void dfs2(int x,int tp){
id[x]=++Idex;hd[x]=tp;
if(Son[x]<0) {el[x]=Idex;return;}
dfs2(Son[x],tp);
for(int point=head[x];point!=-1;point=then){
if(now!=Son[x])
dfs2(now,now);
}
el[x]=Idex;
}
void Push_down(int o,int l,int r){
if(s[o].pt!=-1){
int mid=(l+r)>>1;
s[o<<1].pt=s[o].pt;s[o<<1].sm=(mid-l+1)*s[o].pt;
s[o<<1|1].pt=s[o].pt;s[o<<1|1].sm=(r-mid)*s[o].pt;
s[o].pt=-1;
}
}
void Update(int o){
s[o].sm=s[o<<1].sm+s[o<<1|1].sm;
}
void Modify(int o,int l,int r,int al,int ar,int d){
Push_down(o,l,r);
if(l==al && r==ar){s[o].pt=d,s[o].sm=(r-l+1)*d;return ;}
int mid=(l+r)>>1;
if(al>mid) Modify(o<<1|1,mid+1,r,al,ar,d);
else if(ar<=mid) Modify(o<<1,l,mid,al,ar,d);
else Modify(o<<1|1,mid+1,r,mid+1,ar,d),Modify(o<<1,l,mid,al,mid,d);
Update(o);
}
int Ask(int o,int l,int r,int x){
Push_down(o,l,r);
if(l==r) return s[o].sm;
int mid=(l+r)>>1;
if(x>mid) return Ask(o<<1|1,mid+1,r,x);
return Ask(o<<1,l,mid,x);
}
void Add(int x){
int last;
while(x){
Modify(1,1,n,id[hd[x]],id[x],1);
x=fa[hd[x]];
}
}
void Del(int x){
Modify(1,1,n,id[x],el[x],0);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("manager.in","r",stdin);
freopen("manager.out","w",stdout);
#endif
int x,Kase,sum1,sum2;
char ord[10];
n=in();
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=2;i<=n;i++)
x=in(),addedge(x+1,i),fa[i]=x+1;
dfs1(1);
dfs2(1,1);
Kase=in();
while(Kase--){
scanf("%s%d",ord,&x);x++;
if(ord[0]=='i'){
if(Ask(1,1,n,id[x])==1){puts("0");}
else{
sum1=s[1].sm;
Add(x);
printf("%d\n",s[1].sm-sum1);
}
}
else{
if(Ask(1,1,n,id[x])==0){puts("0");}
else{
sum1=s[1].sm;
Del(x);
printf("%d\n",sum1-s[1].sm);
}
}
}
return 0;
}
T3 寿司晚宴
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4197
有一些数字[2...n],你需要从中选出两个子集A[a1..ak],B[b1..bl],使得这两个子集中没有任何一组i,j满足gcd(ai,bj)!=1,求方案数对p的模值
我们可以对每一个数字考虑,看能不能放到集合A或者集合B。
如果要放到A,显然它的所有因子都不能出现在B,反之亦然。
那么我们可以用状压表示A集合已经有了哪些质因子以及B集合已经有了哪些质因子,然后用f[i][j]表示两边的质因子控制情况,其中i&j==0
然后对于目前元素x,如果x的质因子表示bit(x)&j==0就可以并到i中去,然后大概就是这样子的递推...
当然这样是不行的,因为质因子个数太多了...但是我们发现小于sqrt(500)的质因子只有8个...于是我们就可以状压表示这八个质因子的掌握情况。
首先将所有的数拆分成两个部分,一个是>sqrt(500)的质因子kind,一个是剩下的质因子所构成的一个状压表示。
把所有数按kind分类,如果不存在>sqrt(500)的质因子那就两边都可以放,如果存在的话,对于同一个kind,只能有一个集合取,但是可以取若干个。
那我们就可以设一个p[0][S1][S2],一个p[1][S1][S2]分别表示当前kind由A集合选和由B集合选的两种方案下,同时A集合8个质因子掌握情况为S1,B集合8个质因子掌握情况为S2的方案数。
总的又是一个f[S1][S2]表示A集合和B集合对于8个质因子的掌握情况。
当前加入一个新的数,若其没有>sqrt(500)的质因子或者没有他是一个新的kind分类的开端,就将p[0],p[1]先设置为没有加入这个元素前的状态f[]。然后分别枚举讨论加入后的影响。
若其所在的kind是正在讨论的部分,那么就沿着之前留下的p[0],p[1]接着讨论。因为如果说一个kind考虑完了,f[]需要将两边的元素整合起来,也就是不同kind的考虑是独立的。所以在这个kind下两边的讨论就可以归总到f[]中去。但是p[0]和p[1]都建立在没有选这一类之前,所以要是要整合的话,需要减去在这一类kind之前的选择方案数,否则就被加了两遍。即f[]=p[0]+p[1]-f[];
感觉思路还是挺神奇的.想到分成两个部分的质因子然后用状压的思路值得积累.
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=510;
const int Lim=(1<<8);
const int maxl=(1<<8)+10;
const int Small=19;
int n,mod;
int Prime[9]={0,2,3,5,7,11,13,17,19};
int f[maxl][maxl];
int p[2][maxl][maxl];
struct Node{
int kind,bit;
}s[maxn];
bool cmp(const Node &A,const Node &B){
return A.kind<B.kind;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("dinner.in","r",stdin);
freopen("dinner.out","w",stdout);
#endif
scanf("%d%d",&n,&mod);
for(int i=2;i<=n;i++){
s[i].kind=i;
for(int j=1;j<=8;j++){
if(i%Prime[j]==0){
s[i].bit|=(1<<j-1);
while(s[i].kind%Prime[j]==0) s[i].kind/=Prime[j];
}
}
}
sort(s+2,s+n+1,cmp);
f[0][0]=1;
for(int i=2;i<=n;i++){
if(s[i].kind!=s[i-1].kind || s[i].kind==1){
memcpy(p[0],f,sizeof(f));
memcpy(p[1],f,sizeof(f));
}
for(int S1=Lim-1;S1>=0;S1--)
for(int S2=Lim-1;S2>=0;S2--)
if(!(S1&S2)){
if(!(s[i].bit&S2)) p[0][S1|s[i].bit][S2]=(p[0][S1|s[i].bit][S2]+p[0][S1][S2])%mod;
if(!(s[i].bit&S1)) p[1][S1][S2|s[i].bit]=(p[1][S1][S2|s[i].bit]+p[1][S1][S2])%mod;
}
if(s[i].kind!=s[i+1].kind || s[i].kind==1){
for(int S1=0;S1<Lim;S1++)
for(int S2=0;S2<Lim;S2++)
if(!(S1&S2))
f[S1][S2]=((p[0][S1][S2]+p[1][S1][S2]-f[S1][S2])%mod+mod)%mod;
}
}
int ans=0;
for(int S1=0;S1<Lim;S1++)
for(int S2=0;S2<Lim;S2++)
if(!(S1&S2))
ans=(ans+f[S1][S2])%mod;
printf("%d",ans);
return 0;
}
Day2
T1 荷马史诗
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4198
让你用一个k进制表示n个数,它们分别出现的次数为a1...an,问怎样设计这n个数既使得没有一个是另一个的前缀,又使得这个总的使用字母最短,满足前面两个的基础上,还使得最长的字母最短.
容易让人想起哈夫曼编码.然后这个就是k进制下的,那就是k叉哈夫曼树了...
然后要加几个0保证这棵树长得比较好看呢?...
首先肯定是和k-1有关对吧...因为每k个变成1个,其实就是消失了k-1个.
然后这样理解了之后,就很好弄了,首先设A=n%(k-1)看一下剩几个.因为最后要剩下一个,所以A=1正好,然后其它的就是凑成剩下一个了...
如果A>1,那就是[(n-1)-A]+1=n-A个.如果是A<1,那就是1-A个.
其实也不是没有把上面三个概括起来的方法: 需要+0的个数为: (n-A)%(n-1)个.
模板题+1,良心送分.
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
struct Node{
ll dt;
int lv;
bool operator < (const Node &A) const{
if(A.dt!=dt) return A.dt<dt;
return A.lv<lv;
}
};
int n,k;
priority_queue<Node>q;
int main(){
#ifndef ONLINE_JUDGE
freopen("epic.in","r",stdin);
freopen("epic.out","w",stdout);
#endif
ll x;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lld",&x),q.push((Node){x,1});
x=n%(k-1);
if(x==1) x=0;
else if(x==0) x=1;
else x=k-x;
if(k==2) x=0;
for(int i=1;i<=x;i++) q.push((Node){0,1});
ll ans=0;
Node now;
while(!q.empty()){
now=q.top();q.pop();
if(q.empty()){
printf("%lld\n%d",ans,now.lv-1);
break;
}
for(int i=1;i<k;i++){
now.dt+=q.top().dt;
now.lv=max(now.lv,q.top().lv);
q.pop();
}
now.lv++;
ans+=now.dt;
q.push(now);
}
return 0;
}
T2 品酒大会
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4199
给一个串,设定两个位置i,j为r相似是说,从它们俩开始记为1,往后走r个构成的串都是相同的.每个位置有权值,一个点对的值=它们两个位置的权值之积.
有两问,第一问问你所有的r相似分别有多少个.第二问问你r相似的点对中最大的权值是多少.
然后就联想到了差异这道题...感觉十分的类似.由lca的选择也就可以统计到mx[lca]相似当中去.
这题的计数和那题也是一样的.那么求最大值和最小值联想一下也是可以想出来的.都是树形dp的一部分吧.
但是注意每次计数就只要考虑跨过LCA的贡献,然后就能得到最长为mx[lca]相似的,
最后再累加一下就能得到总共的了.
原题的积累还是比较重要的.
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int in(){
int x=0,f=1;char ch=getchar();
while((ch>'9' || ch<'0') && ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int maxn=600010;
const int INF=0x3f3f3f3f;
typedef long long ll;
int n,last,cnt;
int v[maxn];
int a[maxn][26],mx[maxn],fa[maxn];
int rz[maxn],bg[maxn],sm[maxn],mk[maxn];
int T[maxn],Seq[maxn];
int mk_sm[maxn],mk_bg[maxn];
ll rec1[maxn],rec2[maxn];
char ch[maxn];
void extend(int c,int x){
int p=last,np=last=++cnt;
mx[np]=mx[p]+1,rz[np]=mk[np]=1,bg[np]=sm[np]=mk_sm[np]=mk_bg[np]=v[x];
while(!a[p][c] && p) a[p][c]=np,p=fa[p];
if(!p) fa[np]=1;
else{
int q=a[p][c];
if(mx[q]==mx[p]+1) fa[np]=q;
else{
int nq=++cnt; mx[nq]=mx[p]+1; bg[nq]=mk_bg[nq]=-INF; sm[nq]=mk_sm[nq]=INF;
memcpy(a[nq],a[q],sizeof(a[q]));
fa[nq]=fa[q];
fa[np]=fa[q]=nq;
while(a[p][c]==q) a[p][c]=nq,p=fa[p];
}
}
}
void get_order(){
for(int i=1;i<=cnt;i++) T[mx[i]]++;
for(int i=2;i<=n;i++) T[i]+=T[i-1];
for(int i=1;i<=cnt;i++) Seq[T[mx[i]]--]=i;
}
void Dp(){
int x;
for(int i=cnt;i>=1;i--){
x=Seq[i];
rz[fa[x]]+=rz[x];
bg[fa[x]]=max(bg[fa[x]],bg[x]);
sm[fa[x]]=min(sm[fa[x]],sm[x]);
}
for(int i=1;i<=n;i++) rec2[i]=-0x3f3f3f3f3f3f3f3f;
for(int i=cnt;i>=1;i--){
x=Seq[i];
rec1[mx[fa[x]]]+=(ll)rz[x]*mk[fa[x]];
mk[fa[x]]+=rz[x];
if(mk_bg[fa[x]]!=-INF)
rec2[mx[fa[x]]]=max(rec2[mx[fa[x]]],(ll)mk_bg[fa[x]]*bg[x]);
if(mk_sm[fa[x]]!=INF)
rec2[mx[fa[x]]]=max(rec2[mx[fa[x]]],(ll)mk_sm[fa[x]]*sm[x]);
mk_bg[fa[x]]=max(mk_bg[fa[x]],bg[x]);
mk_sm[fa[x]]=min(mk_sm[fa[x]],sm[x]);
}
int max1=-INF,max2=-INF,min1=INF,min2=INF;
ll ans1=(ll)n*(n-1)/2,ans2;
for(int i=1;i<=n;i++){
if(v[i]>=max1) max2=max1,max1=v[i];
else if(v[i]>max2) max2=v[i];
if(v[i]<=min1) min2=min1,min1=v[i];
else if(v[i]<min2) min2=v[i];
}
if(max2==-INF && min2!=INF) ans2=(ll)min1*min2;
else if(max2!=-INF && min2==INF) ans2=(ll)max1*max2;
else ans2=max((ll)min1*min2,(ll)max1*max2);
printf("%lld %lld\n",ans1,ans2);
for(int i=n-1;i>=1;i--)
rec1[i]+=rec1[i+1],rec2[i]=max(rec2[i],rec2[i+1]);
for(int i=1;i<n;i++){
if(!rec1[i]){printf("0 0\n");}
else
printf("%lld %lld\n",rec1[i],rec2[i]);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("savour.in","r",stdin);
freopen("savour.out","w",stdout);
#endif
last=cnt=1;
n=in();
scanf("%s",ch);
for(int i=0;i<n;i++) v[i]=in();
for(int i=n-1;i>=0;i--) extend(ch[i]-'a',i);
get_order();
Dp();
return 0;
}
T3 小园丁与老司机
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4200
空白让人思考。