NOIP2017提高组解题报告
2017-11-15 17:05
375 查看
D1:
T1:这题考场推1个半小时结论也不知道打表找规律。我还是太菜了。NOIP有史以来的最短代码如下:#include<bits/stdc++.h> using namespace std; long long a,b; int main(){ scanf("%lld%lld",&a,&b); printf("%lld",a*b-a-b); return 0; }
T2:究极大模拟。。考场上心态炸了然后找出答案后写了break,后面的输入不进去了。。真是GG。AC代码其实也特别短,一点也没有斗地主难。注意n相同的判断,用个栈维护就好了。
#include<bits/stdc++.h> using namespace std; int t,hang,zhishu,wuxiao=0,ans,qzhishu; struct chuan{ char bianliang; int zhishu_changshu;//0无效 1指数 2常数 }aaa; stack<chuan>q; bool changshu,shiyong[10000],flag; char fuzadu[100],kaishi[100],jieshu[100]; void xvnhuan(){ cin>>aaa.bianliang>>kaishi>>jieshu; if(!shiyong[int(aaa.bianliang)])shiyong[int(aaa.bianliang)]=1;//是否销毁 else{ if(!flag)printf("ERR\n"); flag=1; } if(kaishi[0]==jieshu[0]&&jieshu[0]=='n'){ q.push((chuan){aaa.bianliang,2}); return ; } int lena=strlen(kaishi),shua=0; int lenb=strlen(jieshu),shub=0; for(int i=0;i<lena;i++){//第一个串 if(kaishi[i]!='n'){ shua*=10; shua+=(int(kaishi[i])-48); } else { q.push((chuan){aaa.bianliang,0}); wuxiao++;//无效。 return ; } } for(int i=0;i<lenb;i++){//第二个串 if(jieshu[i]!='n'){ shub*=10; shub+=(int(jieshu[i])-48); } else if(jieshu[i]=='n'){ q.push((chuan){aaa.bianliang,1}); if(!wuxiao){ qzhishu++; ans=max(ans,qzhishu); } return; } } if(shua<=shub)q.push((chuan){aaa.bianliang,2}); else { q.push((chuan){aaa.bianliang,0}); wuxiao++; }//无效 } void mem(){ qzhishu=0; ans=0; wuxiao=0; flag=0; changshu=0; zhishu=0; memset(fuzadu,0,sizeof(fuzadu)); memset(shiyong,0,sizeof(shiyong)); while(!q.empty())q.pop(); } void Print(){ if(!flag){ if(!q.empty()){printf("ERR\n");return;} if(changshu){ if(!ans)printf("Yes\n"); else printf("No\n"); return ; } else if(!changshu){ if(ans==zhishu)printf("Yes\n"); else printf("No\n"); return ; } } } int main(){ // freopen("tle.in","r",stdin); // freopen("tle.out","w",stdout); scanf("%d",&t); for(int ll=1;ll<=t;ll++){ mem(); scanf("%d%s",&hang,fuzadu); int changdu=strlen(fuzadu); if(fuzadu[2]=='1'){ changshu=1; } else { for(int i=4;i<changdu-1;i++){ zhishu*=10; zhishu=zhishu+int(fuzadu[i])-48; } } for(int i=1;i<=hang;i++){ char leixing; scanf("%s",&leixing); if(leixing=='F')xvnhuan(); else{ if(q.empty()){if(!flag)printf("ERR\n");flag=1;} else{ chuan aaaa=q.top();q.pop(); shiyong[int(aaaa.bianliang)]=0; if(aaaa.zhishu_changshu==0)wuxiao--; if(aaaa.zhishu_changshu==1&&wuxiao==0)qzhishu--; } } } Print(); } return 0; }
T3:考场上没有每组数据memset啊。。真是GG看来爆0了。这题的k只有50,因此我们可以DP刷表。dp【i】【j】表示到i点超出j长度的方案数。 已知dp【u】【0】=1,然后遍历每条边去刷表。这样能拿70分。
100分:拓扑排序判0环 如果0环存在于dis【n】+k以内的路径上输出-1。
我们在DP转移时,因为最短路或0边 是横向转移的(dp【u】【j】=dp【v】【j】)所以我们可以对这些边拓扑排序再DP。
#include<bits/stdc++.h> using namespace std; const int MAXN=1e5+5; int n,m,k,p; struct edge{ int to,next,w; }e[MAXN<<1],fe[MAXN<<1],ling[MAXN<<1]; int head[MAXN],fhead[MAXN],linghead[MAXN],cnt=0,fcnt=0,lingcnt=0; bool flag=0; inline void add(int u,int v,int w){e[++cnt]=(edge){v,head[u],w},head[u]=cnt;} inline void addf(int u,int v,int w){fe[++fcnt]=(edge){v,fhead[u],w},fhead[u]=fcnt;} inline void addling(int u,int v){ling[++lingcnt]=(edge){v,linghead[u],0},linghead[u]=lingcnt;} struct hnd{ int u,d; bool operator<(const hnd&rhs)const{ return d>rhs.d; } }; int dis[MAXN],dis2[MAXN]; priority_queue<hnd>q; void dij(int n){ dis =0; q.push((hnd){n,dis }); while(q.size()){ hnd x=q.top();q.pop(); int u=x.u; if(x.d!=dis[u])continue; for(int i=head[u];i;i=e[i].next){ int v=e[i].to,w=e[i].w; if(dis[u]+w<dis[v]){ dis[v]=dis[u]+w; q.push((hnd){v,dis[v]}); } } } } void dij2(int n){ dis2 =0; q.push((hnd){n,dis2 }); while(q.size()){ hnd x=q.top();q.pop(); int u=x.u; if(x.d!=dis2[u])continue; for(int i=fhead[u];i;i=fe[i].next){ int v=fe[i].to,w=fe[i].w; if(dis2[u]+w<dis2[v]){ dis2[v]=dis2[u]+w; q.push((hnd){v,dis2[v]}); } } } } int que[MAXN],num=0,in[MAXN]; void topsort(){ for(int i=1;i<=n;i++){ for(int j=head[i];j;j=e[j].next){ int v=e[j].to,w=e[j].w; if(!w){ addling(i,v);in[v]++; } } } for(int i=1;i<=n;i++){ if(!in[i])que[++num]=i; } for(int i=1;i<=num;i++){ int u=que[i]; for(int j=linghead[u];j;j=ling[j].next){ int v=ling[j].to; in[v]--;if(!in[v])que[++num]=v; } } for(int i=1;i<=n;i++){ if(in[i]&&dis[i]+dis2[i]<=dis +k){ printf("-1\n"); flag=1; return; } } } int f[60][MAXN]; int dp(){ memset(ling,0,sizeof(ling)); memset(linghead,0,sizeof(linghead)); memset(que,0,sizeof(que)); lingcnt=0,num=0; for(int i=1;i<=n;i++) for(int j=head[i];j;j=e[j].next){ int v=e[j].to,w=e[j].w; if(w+dis[i]==dis[v]){ in[v]++;addling(i,v); } } for(int i=1;i<=n;i++){ if(!in[i]){que[++num]=i;} } for(int i=1;i<=num;i++){ int u=que[i]; for(int j=linghead[u];j;j=ling[j].next){ int v=ling[j].to; in[v]--;if(!in[v])que[++num]=v; } } f[0][1]=1; for(int i=0;i<=k;i++){ for(int x=1;x<=num;x++){ int u=que[x]; for(int z=head[u];z;z=e[z].next) { int v=e[z].to,w=e[z].w; if(i+dis[u]+w-dis[v]<=k) (f[i+dis[u]+w-dis[v]][v]+=f[i][u])%=p; } } } int ans=0; for(int i=0;i<=k;i++) (ans+=f[i] )%=p; return ans; } void mem(){ memset(e,0,sizeof(e)); memset(fe,0,sizeof(fe)); memset(dis,0x3f,sizeof(dis)); memset(dis2,0x3f,sizeof(dis2)); memset(f,0,sizeof(f)); memset(head,0,sizeof(head)); memset(fhead,0,sizeof(fhead)); memset(ling,0,sizeof(ling)); memset(linghead,0,sizeof(linghead)); memset(que,0,sizeof(que)); memset(in,0,sizeof(in)); lingcnt=0,cnt=0,fcnt=0,num=0; flag=0; } void work(){ mem(); scanf("%d%d%d%d",&n,&m,&k,&p); for(int i=1;i<=m;i++){ int tem1,tem2,tem3; scanf("%d%d%d",&tem1,&tem2,&tem3); add(tem1,tem2,tem3); addf(tem2,tem1,tem3); } dij(1); dij2(n); topsort(); if(!flag)printf("%d\n",dp()); } int t; int main(){ freopen("1.in","r",stdin); freopen("1.out","w",stdout); scanf("%d",&t); while(t--)work(); return 0; }
D2 :
T1:考场上一眼就秒的题,可以并查集,可以建图求连通性,听说LL不会被卡,不用开ULL?#include<bits/stdc++.h> using namespace std; const int MAXN=1e4+5; long long a,b,ccfgao,ccfbanjing; int ccffa[MAXN],ccfrk[MAXN],t,ccfdian; struct ccfpoint{ long long chang,kuan,gao; }ppp[MAXN]; inline long long ccfcalc(long long ccfx,long long ccfy ,long long ccfz,long long ccfxx,long long ccfyy,long long ccfzz){ long long ccftem1=abs(ccfx-ccfxx)*abs(ccfx-ccfxx); long long ccftem2=abs(ccfy-ccfyy)*abs(ccfy-ccfyy); long long ccftem3=abs(ccfz-ccfzz)*abs(ccfz-ccfzz); return ccftem1+ccftem2+ccftem3; } void ccfmakeuu(int n){for(int i=0;i<=n;i++)ccffa[i]=i;ccffa[4000]=4000;ccffa[5000]=5000;} int ccffind(int x){ return (x==ccffa[x])?x:ccffa[x]=ccffind(ccffa[x]); } void ccfunset(int x,int y){ if((x=ccffind(x))==(y=ccffind(y)))return; if(ccfrk[x]>ccfrk[y])ccffa[y]=x; else{ ccffa[x]=y; if(ccfrk[x]==ccfrk[y])ccfrk[y]++; } } int main(){ // freopen("cheese.in","r",stdin); // freopen("cheese.out","w",stdout); scanf("%d",&t); for(int l=1;l<=t;l++){ memset(ppp,0,sizeof(ppp)); memset(ccfrk,0,sizeof(ccfrk)); memset(ccffa,0,sizeof(ccffa)); scanf("%d%lld%lld",&ccfdian,&ccfgao,&ccfbanjing); ccfmakeuu(ccfdian); for(int i=1;i<=ccfdian;i++){ scanf("%lld%lld%lld",&ppp[i].chang,&ppp[i].kuan,&ppp[i].gao); if(ppp[i].gao<=ccfbanjing){ ccfunset(i,4000); } if(ccfgao-ppp[i].gao<=ccfbanjing){ ccfunset(i,5000); } } long long ccfbanjing2=(ccfbanjing*ccfbanjing)*4; for(int i=1;i<ccfdian;i++) for(int j=i+1;j<=ccfdian;j++){ if(i==j)continue; if(ccfcalc(ppp[i].chang,ppp[i].kuan,ppp[i].gao,ppp[j].chang,ppp[j].kuan,ppp[j].gao)<=ccfbanjing2) ccfunset(i,j); } if(ccffind(4000)==ccffind(5000))printf("Yes\n"); else printf("No\n"); } return 0; }
T2:这题其实看出状压DP了。。可是状态没有设对(之前也不会枚举子集。 对于第i层操作,状态为【状压表示的二进制码】。 我们可以枚举他的子集。 提前预处理出各种状态(二进制码)到某一个点的最短边。 一种状态可以通过他子集的补集转移来,要走的点就是子集里的点。这题还是很有趣的qaq~注意数组的初始化,别炸long long了。
#include<bits/stdc++.h> using namespace std; const int MAXN=13; const int INF=1<<29; long long n,m,w[MAXN][1<<MAXN],f[MAXN][1<<MAXN]; long long dis[MAXN][MAXN],tem1,tem2,tem3; int main(){ memset(dis,0x3f,sizeof(dis)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d%d%d",&tem1,&tem2,&tem3); dis[tem1][tem2]=min(dis[tem1][tem2],tem3); dis[tem2][tem1]=min(dis[tem2][tem1],tem3); } for(int i=1;i<=n;i++) for(int j=1;j<(1<<n);j++){ w[i][j]=INF; for(int k=1;k<=n;k++){ if(1<<(k-1)&j)w[i][j]=min(w[i][j],dis[k][i]); } } for(int i=1;i<(1<<n);i++){ if(i&(i-1))for(int ll=0;ll<=n;ll++)f[ll][i]=INF; for(int j=i&(i-1);j;j=i&(j-1)){ long long diss=0; for(int k=1;k<=n;k++)if((1<<(k-1))&j){ diss+=w[k][i-j]; // cout<<diss<<" "<<k<<" "<<" "<<i-j<<" "<<j<<endl; } for(int k=1;k<=n;k++)f[k][i]=min(f[k][i],f[k-1][i-j]+diss*k); } } long long ans=0x7fffffffffffffff; for(int i=1;i<=n;i++)ans=min(ans,f[i][(1<<n)-1]); printf("%lld",ans); return 0; }
T3:这题考场上写了个大模拟,听说正解是线段树/树状数组(我怎么看不出来怎么写啊qaq)。用平衡树做比较简单(平衡树不会写啊。。所以我先去学平衡树吧qaq
//平衡树学完了,然后这道题确实挺难的qaq。
我们可以用无旋treap来做这道题,建n+1个treap,会爆空间,怎么办?
我们初始状态每行有一个节点,节点有一个区间从第1人到第m-1人。第n+1个treap维护最后一列,一共n个节点。
这样每个节点都是一个连续的区间,当我们操作一个点他在一个连续区间内时,我们把它拆成三个点。因为询问只有3e5次,这样不会爆空间。然后我们操作的时候那一行和最后一列的treap删除一个点,插入一个点。如果询问的是第m列,那我们特判一下就好了(之前zcy的板子不够简洁我改了一下)
今天(2017年11月21日)才弄懂这道题,还没来得及写代码,代码明天上传。
//2017年11月22日更新
这个题有毒吧qaq 我调了好久啊。。注意pushup!pushup!pushup!重要的事情说三遍,我之前的模板可能是有毒哦qaq。代码里有注释还是很好懂的。这道题一定要注意细节,不要出些奇怪的错。节点信息要记得更新,然后区间长度别算错。连续区间压成一个点,需要时拆开,这是这道题最关键的。然后就没什么了。好的距离比赛时隔10天qaq,我终于写完了今年NOIP pj+tg一共10道题的解题报告。
#include<bits/stdc++.h> using namespace std; #define ll long long #define mp make_pair typedef pair<ll,ll>par; const int MAXN=3e5+5; ll n,m;int q; struct treap{ int rt[MAXN],cnt; int lson[MAXN*6],rson[MAXN*6],prio[MAXN*6]; int size[MAXN*6]; struct que{ ll l,r; }qu[MAXN*6]; inline ll getz(int p){return p==0?0:qu[p].r-qu[p].l+1;} inline void pushup(int p){size[p]=size[lson[p]]+size[rson[p]]+getz(p);} par split(int p,int x){ if(!x)return mp(0,p); int l=lson[p],r=rson[p]; // cout<<size[l]<<endl; if(x<=size[l]){ par tem=split(l,x); lson[p]=tem.second;pushup(p);return mp(tem.first,p); } else if(x>=size[l]+getz(p)){ par tem=split(r,x-size[l]-getz(p)); rson[p]=tem.first;pushup(p);return mp(p,tem.second); } else { x-=size[l]; ll t3=qu[p].r; qu[p].r=qu[p].l+x-1; lson[++cnt]=0;rson[cnt]=0;prio[cnt]=rand();//拆开 qu[cnt].l=qu[p].r+1;qu[cnt].r=t3; int y=cnt; y=merge(y,r);rson[p]=0;//注意把原来区间的右儿子接到cnt上,很容易忘。 pushup(p),pushup(y); return mp(p,y); } } int merge(int x,int y){ if(!x||!y){ pushup(x);pushup(y);return x+y; //zcy模板里没写pushup调了半天啊GG。。。 } if(prio[x]<prio[y]){ rson[x]=merge(rson[x],y);pushup(x);return x; } else { lson[y]=merge(x,lson[y]);pushup(y);return y; } } int work(int x,int y){ par t1=split(rt[x],y-1); //cout<<t1.first<<t1.second; par t2=split(t1.second,1);//t2.first是拆出来的点。 rt[x]=merge(t1.first,t2.second); par t3=split(rt[0],x-1);//分离前x-1个 par t4=split(t3.second,1);//取出第x个作为first准备插入。 // cout<<t4.first<<"ok"<<endl; rt[0]=merge(t3.first,t4.second); rt[x]=merge(rt[x],t4.first); rt[0]=merge(rt[0],t2.first); return t2.first; } int work2(int x){ par t1=split(rt[0],x-1);//前x-1 后x到结束 par t2=split(t1.second,1);//第一个取出的 后面结束 rt[0]=merge(t1.first,t2.second); rt[0]=merge(rt[0],t2.first); return t2.first; } }T; inline void make(ll x){ T.rt[++T.cnt]=T.cnt; T.lson[T.cnt]=0;T.rson[T.cnt]=0;T.prio[T.cnt]=rand(); T.qu[T.cnt].l=m*(x-1)+1,T.qu[T.cnt].r=x*m-1;T.pushup(T.cnt); } void dfs(ll x){ if(!x)return; dfs(T.lson[x]); printf("%lld~~~%lld ",x,T.size[x]); dfs(T.rson[x]); } int main(){ scanf("%lld%lld%d",&n,&m,&q); for(int i=1;i<=n;i++)make(i); T.rt[0]=++T.cnt; T.lson[T.cnt]=0;T.rson[T.cnt]=0;T.prio[T.cnt]=rand();T.qu[T.cnt].l=m;T.qu[T.cnt].r=m;T.pushup(T.cnt); for(int i=2;i<=n;i++){ T.lson[++T.cnt]=0;T.rson[T.cnt]=0;T.prio[T.cnt]=rand();T.qu[T.cnt].l=m*i;T.qu[T.cnt].r=m*i; T.pushup(T.cnt);//千万别忘更新,当时因为size不对,序列就乱了,调了很久。 T.rt[0]=T.merge(T.rt[0],T.cnt);//merge必须返回给根。 } // for(int i=1;i<=n;i++){ // cout<<T.qu[T.rt[i]].l<<"->"<<T.qu[T.rt[i]].r<<endl; // } // par p=T.split(T.rt[5],17); // cout<<T.qu[T.rt[5]].l<<"->"<<T.qu[T.rt[5]].r<<endl; // cout<<T.qu[p.second].l<<"->"<<T.qu[p.second].r<<endl; // dfs(T.rt[0]); // printf("%lld\n",T.qu[T.work(3,3)].l); int tem1,tem2; for(int i=1;i<=q;i++){ scanf("%d%d",&tem1,&tem2); if(tem2!=m)printf("%lld\n",T.qu[T.work(tem1,tem2)].l); else printf("%lld\n",T.qu[T.work2(tem1)].l); } }
相关文章推荐
- NOIP2017提高组解题报告
- 2016 NOIP提高组复赛解题报告 C++
- NOIP2016提高组解题报告
- noip2006提高组-金明的预算方案解题报告
- Noip2016 提高d2 蚯蚓 解题报告
- 2010noip提高组解题报告
- NOIP2009 提高组 最优贸易 解题报告
- 2015 NOIP提高组 复赛解题报告 C++
- NOIP2015提高组复赛 解题报告
- NOIP2015提高组解题报告
- NOIP2006提高组 金明的预算 解题报告
- noipday3提高组模拟题————解题报告
- NOIP2013提高组Day1 解题报告
- NOIP2014 提高组复赛解题报告
- NOIP2016提高组复赛 解题报告
- NOIP2012提高组解题报告
- 【NOIP2013】【提高组】【Day1】【解题报告】
- 夏令营提高班上午上机测试 Day 3 解题报告
- noip2011提高组day1+day2解题报告
- NOIP2013 提高组复赛解题报告