NOIP 2015 提高组 Day1
期望得分:100+100+100=300
实际得分:100+100+45=245
T3 相似的代码 复制过去 没有改全,痛失55分
http://www.cogs.pro/cogs/page/page.php?aid=16
T1 神奇的幻方
题目描述
幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行、每列及两条对角线上的数字之和都相同。
当N为奇数时,我们可以通过以下方法构建一个幻方:
首先将1写在第一行的中间。
之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N):
1.若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列;
2.若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行;
3.若(K−1)在第一行最后一列,则将K填在(K−1)的正下方;
4.若(K−1)既不在第一行,也不在最后一列,如果(K−1)的右上方还未填数,则将K填在(K−1)的右上方,否则将K填在(K−1)的正下方。
现给定N请按上述方法构造N*N的幻方。
输入输出格式
输入格式:
输入文件只有一行,包含一个整数N即幻方的大小。
输出格式:
输出文件包含N行,每行N个整数,即按上述方法构造出的N*N的幻方。相邻两个整数之间用单个空格隔开。
输入输出样例
3
8 1 6 3 5 7 4 9 2
#include<cstdio> using namespace std; int a[100][100],posx[10001],posy[10001]; int main() { freopen("2015magic.in","r",stdin); freopen("2015magic.out","w",stdout); int n; scanf("%d",&n); a[1][n/2+1]=1; posx[1]=1; posy[1]=n/2+1; for(int i=2;i<=n*n;i++) { if(posx[i-1]==1&&posy[i-1]!=n) { posx[i]=n; posy[i]=posy[i-1]+1; a[posx[i]][posy[i]]=i; } else if(posx[i-1]!=1&&posy[i-1]==n) { posx[i]=posx[i-1]-1; posy[i]=1; a[posx[i]][posy[i]]=i; } else if(posx[i-1]==1&&posy[i-1]==n) { posx[i]=posx[i-1]+1; posy[i]=posy[i-1]; a[posx[i]][posy[i]]=i; } else { if(!a[posx[i-1]-1][posy[i-1]+1]) { posx[i]=posx[i-1]-1; posy[i]=posy[i-1]+1; a[posx[i]][posy[i]]=i; } else { posx[i]=posx[i-1]+1; posy[i]=posy[i-1]; a[posx[i]][posy[i]]=i; } } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) printf("%d ",a[i][j]); printf("\n"); } }
T2 信息传递
时间限制:1 s 内存限制:256 MB
【题目描述】
有n个同学(编号为1到n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?
【输入格式】
输入共2行。
第1行包含1个正整数n表示n个人。
第2行包含n个用空格隔开的正整数T1,T2,……,Tn其中第i个整数Ti示编号为i
的同学的信息传递对象是编号为Ti的同学,Ti≤n且Ti≠i
数据保证游戏一定会结束。
【输出格式】
输出共 1 行,包含 1 个整数,表示游戏一共可以进行多少轮。
【样例输入】
5
2 4 2 3 1
【样例输出】
3
【提示】
游戏的流程如图所示。当进行完第 3 轮游戏后, 4 号玩家会听到 2 号玩家告诉他自
己的生日,所以答案为 3。当然,第 3 轮游戏后, 2 号玩家、 3 号玩家都能从自己的消息
来源得知自己的生日,同样符合游戏结束的条件。
对于 30%的数据, n ≤ 200;
对于 60%的数据, n ≤ 2500;
对于 100%的数据, n ≤ 200000。
求最小的强连通分量
tarjen算法 模板
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,cnt,tmp,ans=200005; int to[200001],dfn[200001],low[200001]; struct stack { int topp,st[200001]; bool v[200001]; stack() { topp=0; memset(v,0,sizeof(v));} void push(int x) { st[topp++]=x; v[x]=true; } void pop() { v[topp-1]=false; topp--; } int top() { return st[topp-1]; } bool find(int x) { return v[x]; } }s; void tarjer(int u) { if(!dfn[u]) { s.push(u); dfn[u]=low[u]=++cnt; } if(!dfn[to[u]]) { tarjer(to[u]); low[u]=min(low[to[u]],low[u]); } else if(s.find(to[u])) low[u]=min(low[u],dfn[to[u]]); if(dfn[u]==low[u]) { tmp=0; while(s.top()!=u) { tmp++; s.pop();} tmp++; s.pop(); if(tmp>1) ans=min(tmp,ans); } } int main() { freopen("2015message.in","r",stdin); freopen("2015message.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&to[i]); for(int i=1;i<=n;i++) if(!dfn[i]) tarjer(i); printf("%d",ans); }
T3 斗地主
时间限制:2 s 内存限制:1025 MB
【题目描述】
牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。
具体规则如下:
【输入格式】
第一行包含用空格隔开的2个正整数Tn,表示手牌的组数以及每组手牌的张数。
接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。
【输出格式】
共T行,每行一个整数,表示打光第i手牌的最少次数。
【样例输入1】
1 8 7 4 8 4 9 1 10 4 11 1 5 1 1 4 1 1
【样例输出1】
3
【样例输入2】
1 17 12 3 4 3 2 3 5 4 10 2 3 3 12 2 0 1 1 3 10 1 6 2 12 1 11 3 5 2 12 4 2 2 7 2
【样例输出2】
6
【提示】
样例1说明
共有1组手牌,包含8张牌:方片7,方片8,黑桃9,方片10,黑桃J,黑桃5,方片A以及黑桃A。可以通过打单顺子(方片7,方片8,黑桃9,方片10,黑桃J),单张牌(黑桃5)以及对子牌(黑桃A以及方片A)在3次内打光。
对于不同的测试点, 我们约定手牌组数T与张数n的规模如下:
数据保证:所有的手牌都是随机生成的。
大模拟
先 dfs 顺子
再dfs 几带几
最后在加上 剩余的单牌、对子
一定要加 最优性剪枝:
if(tot > ans) return;
因为这句话,才使上面的 模拟顺序 时间复杂度减少
否则,时间与模拟顺序无关
此代码最后一个点900ms+,刚卡过去
总耗时:1593ms
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int T,n,ans=30; int a[20]; void dfs(int leave,int tot) { if(tot>ans) return; if(!leave) { ans=min(ans,tot); return;} int tmp[20]; memcpy(tmp,a,sizeof(tmp)); int sum=0; bool ok=false; for(int i=3;i<=15;i++) //三顺子 if(a[i]>=3) { sum++; if(sum>=2) { ok=true; break;} } else sum=0; if(ok) { for(int s=3;s<=14-1;s++) //qi dian { if(a[s]<3||a[s+1]<3) continue; a[s]-=3; for(int k=s+1;k<=14;k++) // zhong dian { if(a[k]<3) break; a[k]-=3; dfs(leave-(k-s+1)*3,tot+1); } memcpy(a,tmp,sizeof(a)); } } ok=false; for(int i=3;i<=15;i++)//双顺子 if(a[i]>=2) { sum++; if(sum>=3) { ok=true; break;} } else sum=0; if(ok) { for(int s=3;s<=14-2;s++) //qi dian { if(a[s]<2||a[s+1]<2||a[s+2]<2) continue; a[s]-=2;a[s+1]-=2; for(int k=s+2;k<=14;k++) // zhong dian { if(a[k]<2) break; a[k]-=2; dfs(leave-(k-s+1)*2,tot+1); } memcpy(a,tmp,sizeof(a)); } } ok=false; for(int i=3;i<=15;i++)//单顺子 if(a[i]) { sum++; if(sum>=5) { ok=true; break;} } else sum=0; if(ok) { for(int s=3;s<=14-4;s++) //qi dian { if(!a[s]||!a[s+1]||!a[s+2]||!a[s+3]||!a[s+4]) continue; a[s]--;a[s+1]--;a[s+2]--;a[s+3]--; for(int k=s+4;k<=14;k++) // zhong dian { if(!a[k]) break; a[k]--; dfs(leave-(k-s+1),tot+1); } memcpy(a,tmp,sizeof(a)); } } for(int i=2;i<=14;i++) // 四带X { if(a[i]<4) continue; for(int j=0;j<=14;j++) //四带二对 第一对 { if(a[j]<2||j==i) continue; for(int k=j+1;k<=14;k++) //第二对 { if(a[k]<2||k==i) continue; a[j]-=2; a[k]-=2;a[i]-=4; dfs(leave-8,tot+1); memcpy(a,tmp,sizeof(a)); } a[j]-=2;a[i]-=4; //四带一对 dfs(leave-6,tot+1); memcpy(a,tmp,sizeof(a)); } for(int j=0;j<14;j++) //四带两张单 第一张 { if(!a[j]||j==i) continue; for(int k=j+1;k<=14;k++) //第二张 { if(!a[k]||k==i) continue; a[j]--; a[k]--;a[i]-=4; dfs(leave-6,tot+1); memcpy(a,tmp,sizeof(a)); } } a[i]-=4; //炸弹 dfs(leave-4,tot+1); memcpy(a,tmp,sizeof(a)); } for(int i=2;i<=14;i++) //三带二 第一对 { if(a[i]<3) continue; for(int j=0;j<=14;j++) //第二对 { if(a[j]<2||j==i) continue; a[i]-=3; a[j]-=2; dfs(leave-5,tot+1); memcpy(a,tmp,sizeof(a)); } for(int j=0;j<=14;j++) //三带一 { if(!a[j]||j==i) continue; a[i]-=3; a[j]--; dfs(leave-4,tot+1); memcpy(a,tmp,sizeof(a)); } a[i]-=3;//三张牌 dfs(leave-3,tot+1); memcpy(a,tmp,sizeof(a)); } int p=0; for(int i=0;i<=14;i++) //剩余的 if(a[i]) p++; dfs(0,tot+p); memcpy(a,tmp,sizeof(a)); } void clear() { ans=30; memset(a,0,sizeof(a)); } int main() { freopen("landlords.in","r",stdin); freopen("landlords.out","w",stdout); scanf("%d%d",&T,&n); int x; while(T--) { clear(); for(int i=1;i<=n;i++) { scanf("%d",&x); if(x==1) x=14; a[x]++; scanf("%d",&x); } dfs(n,0); printf("%d\n",ans); } }
优化:
在上一份代码中,如果是 几带几
dfs枚举它带的是哪张牌
若 它带的牌 不能形成牌型,那么带谁都是一样的
所以我们可以与处理出 哪那些单牌、对子不能形成顺子
那么这些牌是没用的
即,若不能带出去,只能一次一次出
因为这些牌用谁无所谓
所以用两个变量记录他们的总数即可
dfs要带牌时,
如果 还有没用的的单牌、对子 就先用这些
否则,dfs枚举 其他单牌、对子
所以dfs只需要考虑如何打光 能形成牌型的牌,
最后再加上 没有带出的没用的对子、单牌即可
总耗时:436ms
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int T,n,ans=30; int a[20]; int dan,dui; bool v[20]; void dfs(int leave,int tot) { if(tot>ans) return; if(!leave) { ans=min(ans,tot+dan+dui); return;} int tmp[20]; memcpy(tmp,a,sizeof(tmp)); int sum=0; bool ok=false; for(int i=3;i<=15;i++) if(a[i]>=3) { sum++; if(sum>=2) { ok=true; break;} } else sum=0; if(ok) { for(int s=3;s<=14-1;s++) { if(a[s]<3||a[s+1]<3) continue; a[s]-=3; for(int k=s+1;k<=14;k++) { if(a[k]<3) break; a[k]-=3; dfs(leave-(k-s+1)*3,tot+1); } memcpy(a,tmp,sizeof(a)); } } ok=false; for(int i=3;i<=15;i++) if(a[i]>=2) { sum++; if(sum>=3) { ok=true; break;} } else sum=0; if(ok) { for(int s=3;s<=14-2;s++) { if(a[s]<2||a[s+1]<2||a[s+2]<2) continue; a[s]-=2;a[s+1]-=2; for(int k=s+2;k<=14;k++) { if(a[k]<2) break; a[k]-=2; dfs(leave-(k-s+1)*2,tot+1); } memcpy(a,tmp,sizeof(a)); } } ok=false; for(int i=3;i<=15;i++) if(a[i]) { sum++; if(sum>=5) { ok=true; break;} } else sum=0; if(ok) { for(int s=3;s<=14-4;s++) { if(!a[s]||!a[s+1]||!a[s+2]||!a[s+3]||!a[s+4]) continue; a[s]--;a[s+1]--;a[s+2]--;a[s+3]--; for(int k=s+4;k<=14;k++) { if(!a[k]) break; a[k]--; dfs(leave-(k-s+1),tot+1); } memcpy(a,tmp,sizeof(a)); } } //以上代码都与上一个代码一模一样,只有这儿下面的改了 int use_dui[20],use_dan[20]; //统计 除去预处理出的没用的单牌、对子外,其他的单排、对子 use_dui[0]=0; use_dan[0]=0; for(int i=0;i<=14;i++) if(v[i]) { if(a[i]) use_dan[++use_dan[0]]=i; if(a[i]>=2) use_dui[++use_dui[0]]=i; } for(int i=2;i<=14;i++) { if(a[i]<4) continue;//先四带两对 if(dui>=2) //没用的对子至少有两对,带出去 { dui-=2; a[i]-=4; dfs(leave-4,tot+1); dui+=2; a[i]+=4; } else //没用的对子不够两对,枚举统计出来的其他的对子 { for(int j=1;j<=use_dui[0];j++) { if(use_dui[j]==i) continue; for(int k=j+1;k<=use_dui[0];k++) { if(use_dui[k]==i) continue; a[i]-=4; a[use_dui[j]]-=2; a[use_dui[k]]-=2; dfs(leave-8,tot+1); a[i]+=4; a[use_dui[j]]+=2; a[use_dui[k]]+=2; } bool g=false; //如果没用的对子 还有一对,那么当然就带出去,还是四带两对,否则四带一对 if(dui==1) { g=true; dui=0;} a[i]-=4; a[use_dui[j]]-=2; dfs(leave-6,tot+1); if(g) dui=1; a[i]+=4; a[use_dui[j]]+=2; } } if(dan>=2) //四带两张单牌,同对子 { dan-=2; a[i]-=4; dfs(leave-4,tot+1); dan+=2; a[i]+=4; } else { for(int j=1;j<=use_dan[0];j++) { if(use_dan[j]==i) continue; for(int k=j+1;k<=use_dan[0];k++) { if(use_dan[k]==i) continue; a[i]-=4; a[use_dan[j]]--; a[use_dan[k]]--; dfs(leave-6,tot+1); a[i]+=4; a[use_dan[j]]++; a[use_dan[k]]++; } if(dan==1) { dan=0; a[i]-=4; a[use_dan[j]]--; dfs(leave-5,tot+1); dan=1; a[i]+=4; a[use_dan[j]]++; } } } } for(int i=2;i<=14;i++) //三带的情况,同四带 { if(a[i]<3) continue; if(dui) { dui--; a[i]-=3; dfs(leave-3,tot+1); dui++; a[i]+=3; } else { for(int j=1;j<=use_dui[0];j++) { if(use_dui[j]==i) continue; a[i]-=3; a[use_dui[j]]-=2; dfs(leave-5,tot+1); a[i]+=3; a[use_dui[j]]+=2; } } if(dan) { dan--; a[i]-=3; dfs(leave-3,tot+1); dan++; a[i]+=3; } else { for(int j=1;j<=use_dan[0];j++) { if(use_dan[j]==i) continue; a[i]-=3; a[use_dan[j]]--; dfs(leave-4,tot+1); a[i]+=3; a[use_dan[j]]++; } } } int h=0; for(int i=0;i<=14;i++) //一次出一种的牌,包括单张、对子、三张牌、炸弹 if(a[i]) h++; dfs(0,tot+h); } void pre() { for(int i=3;i<=14;i++) { if(v[i]||!a[i]) continue; if(a[i]>=1) { int j=i; while(a[j]) j++; j--; if(j-i+1>=5) for(int k=i;k<=j;k++) v[k]=true; } if(a[i]>=2) { int j=i; while(a[j]>=2) j++; j--; if(j-i+1>=3) for(int k=i;k<=j;k++) v[k]=true; } } dan=0; dui=0; for(int i=0;i<=14;i++) if(!v[i]) { if(a[i]==1) a[i]=0,dan++; else if(a[i]==2) a[i]=0,dui++; else if(a[i]) v[i]=true; } } int main() { freopen("landlords.in","r",stdin); freopen("landlords.out","w",stdout); scanf("%d%d",&T,&n); int x; while(T--) { ans=30; memset(a,0,sizeof(a)); memset(v,0,sizeof(v)); for(int i=1;i<=n;i++) { scanf("%d",&x); if(x==1) x=14; a[x]++; scanf("%d",&x); } pre(); dfs(n-dan-dui*2,0); printf("%d\n",ans); } }
考试时,模拟顺子代码
只写了 三顺子,双顺子、单顺子复制的
但没改全,导致双顺子里牌数-3
虽然也写了优化的代码,但只优化了几带几部分
所以顺子那块也是直接复制的
所以也错了
痛失55分,否则今日题目就可以AK了。
复制一定要慎重
看了看以前写的代码,竟然在洛谷上跑11ms
#include<cstdio> #include<cstring> using namespace std; int t,n,a[20],b[5],ans=0x7fffffff; void dfs(int); void pre() { ans=0x7fffffff; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); } void shunzi(int now) { for(int i=3;i<=14;i++) { int j=i; while(a[j]>=1) j++; if(j-i>=5) { for(int h=i+4;h<=j-1;h++) { for(int k=i;k<=h;k++) a[k]--; dfs(now+1); for(int k=i;k<=h;k++) a[k]++; } } } } void liandui(int now) { for(int i=3;i<=14;i++) { int j=i; while(a[j]>=2) j++; if(j-i>=3) { for(int h=i+2;h<=j-1;h++) { for(int k=i;k<=h;k++) a[k]-=2; dfs(now+1); for(int k=i;k<=h;k++) a[k]+=2; } } } } void feiji(int now) { for(int i=3;i<=14;i++) { int j=i; while(a[j]>=3) j++; if(j-i>=2) { for(int h=i+1;h<=j-1;h++) { for(int k=i;k<=h;k++) a[k]-=3; dfs(now+1); for(int k=i;k<=h;k++) a[k]+=3; } } } } int qita() { memset(b,0,sizeof(b)); int tot=0; for(int i=3;i<=17;i++) b[a[i]]++; while(b[4]>=1&&b[2]>=2) b[2]-=2,b[4]--,tot++; while(b[4]>=1&&b[1]>=2) b[1]-=2,b[4]--,tot++; while(b[4]>=1&&b[2]>=1) b[2]--,b[4]--,tot++; while(b[3]>=1&&b[2]>=1) b[3]--,b[2]--,tot++; while(b[3]>=1&&b[1]>=1) b[3]--,b[1]--,tot++; return tot+b[1]+b[2]+b[3]+b[4]; } void dfs(int now) { if(now>=ans) return; int tmp=qita(); if(now+tmp<ans) ans=now+tmp; feiji(now); liandui(now); shunzi(now); } int main() { scanf("%d%d",&t,&n); while(t--) { pre(); for(int i=1;i<=n;i++) { int x,y; scanf("%d%d",&x,&y); if(x==2) x=16; if(x==1) x=14; if(x==0) x=17; a[x]++; } dfs(0); printf("%d\n",ans); } }
今天发现那是错误的
因为 在qita()函数里
几张牌就只能按几张牌出
但事实上是 若3张牌,可以按对子+单牌出
官方数据竟然没卡掉
数据:
1 9
3 1
3 2
3 3
5 1
5 2
5 3
8 1
8 2
8 3
输出:2