CF#403(Div.2) 解题报告

CF#403(Div.2) 解题报告

A

题意简述

有2n双袜子,编号为1..n。 按顺序从包中拿出,如果这只袜子的另一只还没有拿出,就放在桌子上,否则将桌子上的另一只拿走。 求桌子上最多有多少只袜子。

数据范围

1≤n≤105

题解

模拟。。

代码

#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> #include<ctime> #include<cmath> #include<map> using namespace std; int n,x,now,ans; int cnt[100005]; int main() { scanf("%d",&n); for (int i=1;i<=2*n;++i) { scanf("%d",&x); if (!cnt[x]) { ++now; ++cnt[x]; } else --now; ans=max(ans,now); } PRintf("%d\n",ans); }

B

题意简述

有n个人,站在一个数轴上,位置分别为x1..xn。 每个人有一个速度v1..vn,他可以向左走或者向右走。 求所有的人走到同一个点的最短时间(注意这个点不一定是整点)。 special judge 相对误差不超过10−6

数据范围

2≤n≤60000

题解

实数二分时间mid 每一个人在mid的时间里能走到的范围是一段区间 判断n个区间是否有交集即可

代码

#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> #include<ctime> #include<cmath> #include<map> using namespace std; const double eps=1e-6; int n; double x[60005],v[60005],ans; bool check(double mid) { double Min=1e10,Max=-1e10; for (int i=1;i<=n;++i) { double l=x[i]-v[i]*mid,r=x[i]+v[i]*mid; Min=min(Min,r);Max=max(Max,l); } return Min-Max>eps; } double find() { double l=0.0,r=1e9,mid,ans=0; while (r-l>eps) { mid=(l+r)/2.0; if (check(mid)) ans=r=mid; else l=mid; } return ans; } int main() { scanf("%d",&n); for (int i=1;i<=n;++i) scanf("%lf",&x[i]); for (int i=1;i<=n;++i) scanf("%lf",&v[i]); ans=find(); printf("%.12lf\n",ans); }

C

题意简述

一棵树,将其染色,任意有两条边相连的3元组3个点都不能染同一种颜色。 求符合要求的最少的颜色,构造方案。

数据范围

3≤n≤2⋅105

题解

显然答案为度数最大的点的度+1 构造方案的话,显然对于一个点来说,它所有的儿子都不能染相同的颜色,并且这些颜色和他自己以及他父亲的颜色都不能相同 dfs的过程中染色即可

代码

#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> #include<ctime> #include<cmath> #include<map> using namespace std; #define N 200005 int n,x,y,ans; int d[N],c[N]; int tot,point[N],nxt[N*2],v[N*2]; void add(int x,int y) { ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; } void dfs(int x,int fa) { int now=0; for (int i=point[x];i;i=nxt[i]) if (v[i]!=fa) { ++now; while (now==c[fa]||now==c[x]) ++now; c[v[i]]=now; } for (int i=point[x];i;i=nxt[i]) if (v[i]!=fa) dfs(v[i],x); } int main() { scanf("%d",&n); for (int i=1;i<n;++i) { scanf("%d%d",&x,&y); add(x,y),add(y,x); ++d[x];++d[y]; ans=max(ans,d[x]);ans=max(ans,d[y]); } printf("%d\n",ans+1); c[1]=1;dfs(1,0); for (int i=1;i<=n;++i) printf("%d%c",c[i]," \n"[i==n]); }

D

题意简述

有n个队伍,每一个队伍有一个名字,是两个字符串。 现在需要你给每一个队伍的名字一个缩写,这个缩写的方式有两种:1、第一个字符串的前三个字符 2、第一个字符串的前两个字符+第二个字符串的第一个字符。 如果有一个队伍选择了第二种方式,那么不能存在某一个队伍选择的第一个方式和这个队伍的第一个方式相同。 求一种方案使所有队伍的缩写互异。

数据范围

1≤n≤1000,每个字符串长度≤20

题解

典型的2-sat问题 枚举两支队伍,如果:1、第一种相同,那么必须都选第二种 2、第二种相同,不能同时选第二种 3、第一种和第二种相同,不能同时选第一种和第二种 建图缩点判断可行性 最后dfs构造合法解就行了

代码

#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> #include<ctime> #include<cmath> #include<map> using namespace std; #define N 1005 int n;char s[N]; struct data{char s[10];}str[N][5]; int tot,point[N*2],nxt[N*N*8],v[N*N*8]; int dfn[N*2],low[N*2],stack[N*2],belong[N*2],ch[N*2],last[N*2],top,dfs_clock,scc; bool flag,vis[N*2]; bool check(char *a,char *b) { for (int i=1;i<=3;++i) if (a[i]!=b[i]) return 0; return 1; } void add(int x,int y) { ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; } void tarjan(int x) { dfn[x]=low[x]=++dfs_clock;stack[++top]=x;vis[x]=1; for (int i=point[x];i;i=nxt[i]) if (!dfn[v[i]]) { tarjan(v[i]); low[x]=min(low[x],low[v[i]]); } else if (vis[v[i]]) low[x]=min(low[x],dfn[v[i]]); if (dfn[x]==low[x]) { int now=0;++scc; while (now!=x) { now=stack[top--]; belong[now]=scc; vis[now]=0; } } } void dfs(int x) { vis[x]=1; if (ch[x]==-1) { flag=0; return; } stack[++top]=x;last[x]=ch[x];ch[x]=1; if (x<=n) stack[++top]=x+n,last[x+n]=ch[x+n],ch[x+n]=-1; else stack[++top]=x-n,last[x-n]=ch[x-n],ch[x-n]=-1; for (int i=point[x];i&&flag;i=nxt[i]) if (!vis[v[i]]) dfs(v[i]); } int main() { scanf("%d",&n); for (int i=1;i<=n;++i) { scanf("%s",s);str[i][1].s[1]=s[0],str[i][1].s[2]=s[1],str[i][1].s[3]=s[2]; scanf("%s",s);str[i][2]=str[i][1];str[i][2].s[3]=s[0]; } for (int i=1;i<=n;++i) for (int j=i+1;j<=n;++j) { if (check(str[i][1].s,str[j][1].s)) add(i,n+i),add(j,n+j); if (check(str[i][2].s,str[j][2].s)) add(n+i,j),add(n+j,i); if (check(str[i][1].s,str[j][2].s)) add(i,j),add(n+j,n+i); if (check(str[i][2].s,str[j][1].s)) add(n+i,n+j),add(j,i); } for (int i=1;i<=n;++i) if (!dfn[i]) tarjan(i); for (int i=1;i<=n;++i) if (belong[i]==belong[n+i]) { puts("NO"); return 0; } for (int i=1;i<=n;++i) { if (ch[i]) continue; memset(vis,0,sizeof(vis)); top=0; flag=1;dfs(i); if (!flag) { for (int j=1;j<=top;++j) ch[stack[j]]=last[stack[j]]; } } puts("YES"); for (int i=1;i<=n;++i) { if (ch[i]==1) for (int j=1;j<=3;++j) putchar(str[i][1].s[j]); else for (int j=1;j<=3;++j) putchar(str[i][2].s[j]); puts(""); } }

E

题意简述

一个n个点m条边的无向图,问是否能用至多k条路径覆盖每一个点,并且每条路径的长度不超过⌈2nk⌉

数据范围

1≤n≤2⋅105,n−1≤m≤2⋅105,1≤k≤n

题解

这题就是道神经病题。。 dfs过程中记录每一个点的入栈和出栈两次,保证任意的路径不断 然后计算个数输出就行了 题目中每条路径的长度不超过⌈2nk⌉的限制保证了这种做法的正确性

代码

#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> #include<ctime> #include<cmath> #include<map> using namespace std; #define N 400005 int n,m,k,x,y; int tot,point[N],nxt[N],v[N]; bool vis[N]; int ans[N]; void add(int x,int y) { ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; } void dfs(int x) { vis[x]=1;ans[++ans[0]]=x; for (int i=point[x];i;i=nxt[i]) if (!vis[v[i]]) { dfs(v[i]); ans[++ans[0]]=x; } } int main() { scanf("%d%d%d",&n,&m,&k); for (int i=1;i<=m;++i) { scanf("%d%d",&x,&y); add(x,y),add(y,x); } dfs(1);ans[0]=unique(ans+1,ans+ans[0]+1)-ans-1; int now=1; for (int i=1;i<=k;++i) { if (ans[0]-now+1>=(2*n+k-1)/k) { printf("%d ",(2*n+k-1)/k); for (int j=now;j<=now+(2*n+k-1)/k-1;++j) printf("%d%c",ans[j]," \n"[j==now+(2*n+k-1)/k-1]); now=now+(2*n+k-1)/k; } else if (now<=ans[0]) { printf("%d ",ans[0]-now+1); for (int j=now;j<=ans[0];++j) printf("%d%c",ans[j]," \n"[j==ans[0]]); now=ans[0]+1; } else puts("1 1"); } }

F

题意简述

有一个n个点m条边的有向图,每一条边可能是0或1两种类型。 没有自环,两个点之间也不可能有类型相同的两条边。 构造一个序列,方法是:第一个元素为0,之后的每一次将其复制一遍接到后面然后再将复制的部分取反。 栗子:0,01,0110,01101001… 要求在图上按照这个序列走,问走的路径的最长长度。 如果答案>1018,输出-1

数据范围

1 ≤ n ≤ 500,0 ≤ m ≤ 2n2

题解

非常强的一道dp。。。 有点类似倍增的思想 f(i,j,k,l)表示走的第一条边类型为i,走的长度为2j,起点为k,能走到的点的状态为l 是否有可行的方案 因为是取反,所以dp的时候每一次要反过来接上 求出f了之后如何统计答案呢? 令p表示当前的答案能到达的点,然后枚举每一个点,如果能到达,再用f去更新下一个p dp的过程中用bitset加速

代码

#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<queue> #include<ctime> #include<cmath> #include<bitset> #include<map> using namespace std; #define N 505 int n,m,x,y,z,type; long long ans; bitset <N> f[2][65][N],p,q; int main() { scanf("%d%d",&n,&m); for (int i=1;i<=m;++i) { scanf("%d%d%d",&x,&y,&z); f[z][0][x][y]=1; } for (int i=1;i<=60;++i) for (int j=1;j<=n;++j) for (int k=1;k<=n;++k) { if (f[0][i-1][j][k]) f[0][i][j]|=f[1][i-1][k]; if (f[1][i-1][j][k]) f[1][i][j]|=f[0][i-1][k]; } for (int i=1;i<=n;++i) if (f[0][60][1][i]) { puts("-1"); return 0; } type=0;ans=0LL;p[1]=1; for (int i=60;i>=0;--i) { q.reset(); for (int j=1;j<=n;++j) if (p[j]) q|=f[type][i][j]; if (q.any()) type^=1,ans+=1LL<<i,p=q; if (ans>1e18) break; } if (ans>1e18) puts("-1"); else printf("%I64d\n",ans); }