2020-07-02 热身训练赛(二)
A.边权为lcm的最小生成树(HDU 5922)
题意:一共有n个节点,编号从1到n,若在两个节点之间连边,则边权为两节点权值的最小公倍数,求该图的最小生成树的边权之和。
解:每个节点加到树里,需要使添加的边权最小,每个数字与1的最小公倍数是这个数字本身,因此为最小边权,所以只需要将每个节点与节点1相邻即可。此时的答案最小,为2~n的和,即$\frac{(2+n)(n-1)}{2}$
#include<iostream> #include<cstdio> using namespace std; long long n,ans; int main(){ int T; scanf("%d",&T); for(int Case=1;Case<=T;Case++){ scanf("%lld",&n); ans=(2+n)*(n-1)/2LL; printf("Case #%d: %lld\n",Case,ans); } return 0; }
B.HDU 5923
C.不等式(HDU 5924)
题意:给出两个整数$A,B(1\leq A\leq B\leq 10^{18})$,求出有哪些整数对$C,D(A\leq C,D\leq B)$,满足$\frac{A}{B} + \frac{B}{A} \leq \frac{C}{D} + \frac{D}{C}$。
解:把$\frac{A}{B}$看作一个整体,为w,$\frac{C}{D}$也看作一个整体v(假设$C\geq D$,如果$D\geq C$,那么就令$v=\frac{D}{C}$,实际上都是一个道理),那么不等式可以写为$w+\frac{1}{w} \leq v+\frac{1}{v}$,因为C和D是A和B之间的值,所以$w\geq v$,且$w,v\geq 1$,由对号函数的图像可知,始终存在$w+\frac{1}{w}\geq v+\frac{1}{v}$,所以只有当$w=v$时有$w+\frac{1}{w}\leq v+\frac{1}{v}$,此时有$A=C,B=D或者A=D,B=C$。
#include<iostream> #include<cstdio> using namespace std; int T; long long A,B; int main(){ scanf("%d",&T); for(int Case=1;Case<=T;Case++){ printf("Case #%d:\n",Case); scanf("%lld%lld",&A,&B); if(A==B){ puts("1"); printf("%lld %lld\n",A,B); } else { if(A>B)swap(A,B); puts("2"); printf("%lld %lld\n%lld %lld\n",A,B,B,A); } } return 0; }
D.连通块(HDU 5925)
题意:有个n*m的网格(长宽最大为$10^{9}$),有一些格子内有障碍(最多200个),问有多少四连通块,每个连通块内的格子数
解:离散化,将行和列分开进行离散化,拿行来讲,就是每个有障碍的行都要抽出来,两个有障碍的行之间的空白行离散为1行,这样的一行有一个权值,就是它对应的原来的行数,同理,列也要这样离散,直到将网格图离散为最多200*200的大小,就可以bfs了,还要注意一点,一个障碍没有的情况容易出错,最好特判一下。这个题和19年12月csp的第二题好像,当时那个题也做出来了,就是代码写的好啰嗦(捂脸脸)。
#include<iostream> #include<cstdio> #include<cstring> #include<map> #include<algorithm> #define maxn 210 using namespace std; int T,num,px[maxn],py[maxn],cntx,cnty,ans,n,m; long long wx[maxn*2],wy[maxn*2],q[maxn*maxn*4]; bool f[maxn*2][maxn*2]; map<int,int>ox; map<int,int>oy; struct point{ int x,y; }p[maxn]; void hx(){ for(int i=1;i<=num;i++){ if(px[i]==px[i-1])continue; if(i==1&&px[i]!=1){ cntx++; wx[cntx]=px[i]-1; } if(i!=1&&px[i]!=px[i-1]+1){ cntx++; wx[cntx]=px[i]-px[i-1]-1; } cntx++; wx[cntx]=1; ox[px[i]]=cntx; } if(px[num]!=n){ cntx++; wx[cntx]=n-px[num]; } } void hy(){ for(int i=1;i<=num;i++){ if(py[i]==py[i-1])continue; if(i==1&&py[i]!=1){ cnty++; wy[cnty]=py[i]-1; } if(i!=1&&py[i]!=py[i-1]+1){ cnty++; wy[cnty]=py[i]-py[i-1]-1; } cnty++; wy[cnty]=1; oy[py[i]]=cnty; } if(py[num]!=m){ cnty++; wy[cnty]=m-py[num]; } } void dfs(int x,int y){ q[ans]+=wx[x]*wy[y]; f[x][y]=1; if(x>1&&!f[x-1][y])dfs(x-1,y); if(y>1&&!f[x][y-1])dfs(x,y-1); if(x<cntx&&!f[x+1][y])dfs(x+1,y); if(y<cnty&&!f[x][y+1])dfs(x,y+1); } void init(){ memset(f,0,sizeof(f)); memset(q,0,sizeof(q)); ans=0; cntx=0; cnty=0; } int main(){ scanf("%d",&T); for(int Case=1;Case<=T;Case++){ init(); scanf("%d%d",&n,&m); scanf("%d",&num); if(num==0){ int e=0; } if(num==0){ ans=1; printf("Case #%d:\n",Case); printf("%d\n",ans); long long www=1LL*n*m; printf("%lld",www); puts(""); continue; } for(int i=1;i<=num;i++){ scanf("%d%d",&p[i].x,&p[i].y); px[i]=p[i].x;py[i]=p[i].y; } sort(px+1,px+num+1); sort(py+1,py+num+1); hx();hy(); for(int i=1;i<=num;i++){ int x=ox[p[i].x]; int y=oy[p[i].y]; f[x][y]=1; } for(int i=1;i<=cntx;i++){ for(int j=1;j<=cnty;j++){ if(!f[i][j]){ ans++; dfs(i,j); } } } printf("Case #%d:\n",Case); printf("%d\n",ans); sort(q+1,q+ans+1); for(int i=1;i<ans;i++)printf("%lld ",q[i]); printf("%lld\n",q[ans]); } return 0; }
E.连连看(HDU 5926)
题意:给出一个连连看的初始状态,判断能否连一次。
解:只需要判断四面和内部是否有可以连的
#include<iostream> #include<cstdio> #include<cstring> #include<map> using namespace std; int a[31][31]; bool flag; map<int,bool>vis; int main(){ int T; int n,m; scanf("%d",&T); for(int Case=1;Case<=T;Case++){ printf("Case #%d: ",Case); flag=0; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); vis.clear(); for(int i=1;i<=m;i++){ if(vis[a[1][i]])flag=1; vis[a[1][i]]=1; } if(flag){puts("Yes");continue;} vis.clear(); for(int i=1;i<=m;i++){ if(vis[a[n][i]])flag=1; vis[a[n][i]]=1; } if(flag){puts("Yes");continue;} vis.clear(); for(int i=1;i<=n;i++){ if(vis[a[i][1]])flag=1; vis[a[i][1]]=1; } if(flag){puts("Yes");continue;} vis.clear(); for(int i=1;i<=n;i++){ if(vis[a[i][m]])flag=1; vis[a[i][m]]=1; } if(flag){puts("Yes");continue;} for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i][j]==a[i-1][j]&&i>1)flag=1; if(a[i][j]==a[i+1][j]&&i<n)flag=1; if(a[i][j]==a[i][j-1]&&j>1)flag=1; if(a[i][j]==a[i][j+1]&&j<m)flag=1; } } if(flag){puts("Yes");continue;} if(!flag){puts("No");continue;} } }
F.HDU 5927
G.HDU 5928
H.nand(HDU 5929)
题意:定义一种位运算nand,满足$\\ 0 nand 0=1 \\ 0 nand1=1 \\ 1 nand 0=1 \\ 1 nand 1=0\\ $。要求实现一个栈,可以向栈顶加元素(只为1或0),弹出栈顶元素,求从栈顶到栈底的元素nand和,把栈反转,每次查询要求输出nand和。
解:用队列代替栈,反转操作就定义一个标记,把标记的1和0作为此时栈正或反的状态,添加元素和弹出元素就可以根据标记值决定加在队列头或者尾。求nand和时先看一下规律,发现再nand运算下,所有数字nand 0都得1,那么对于一个序列,从头nand过来,不管前面的值为多少,只要读到0,当前结果就为1。所以计算时只需要找到最后一个0的位置,把它当作1(如果只有一个0,而且在栈顶,就需要特判)。然后又发现$\\ 1=1 \\ 1 nand 1=0 \\ 1 nand 1 nand 1=1 \\ 1 nand 1 nand 1 nand 1=0\\$可以发现奇数个1的nand和为1,偶数个1的nand和为0,就可以结合末尾1的个数来求总nand和了。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #define maxn 200010 using namespace std; int q[maxn*2],id[maxn*2],qcnt[2],idcnt[2]; bool flag=1; char s[10]; int main(){ freopen("Cola.txt","r",stdin); int T,x,n; scanf("%d",&T); for(int Case=1;Case<=T;Case++){ qcnt[1]=idcnt[1]=200002; qcnt[0]=idcnt[0]=200003; flag=1; printf("Case #%d:\n",Case); scanf("%d",&n); while(n--){ scanf("%s",s); if(s[2]=='S'){ scanf("%d",&x); if(flag)q[++qcnt[flag]]=x; else q[--qcnt[flag]]=x; if(x==0){ if(flag)id[++idcnt[flag]]=qcnt[flag]; else id[--idcnt[flag]]=qcnt[flag]; } } else if(s[2]=='P'){ if(q[qcnt[flag]]==0){ if(flag)idcnt[flag]--; else idcnt[flag]++; } if(flag)qcnt[flag]--; else qcnt[flag]++; } else if(s[2]=='V'){ flag=!flag; } else if(s[2]=='E'){ if(qcnt[1]<qcnt[0]){ puts("Invalid."); } else if(qcnt[0]==qcnt[1]){ printf("%d\n",q[qcnt[0]]); } else if(idcnt[1]<idcnt[0]){ printf("%d\n",(qcnt[1]-qcnt[0]+1)%2); } else if(idcnt[1]-idcnt[0]==0&&q[qcnt[flag]]==0){ printf("%d\n",(qcnt[1]-qcnt[0])%2); } else{ printf("%d\n",(abs(qcnt[!flag]-id[idcnt[!flag]])+1)%2); } } } } return 0; }
I.HDU 5930
J.HDU 5931
K.HDU 5932