FJ2016集训 day7
题目来自coolinging (orz)
Problem 1 挑选子序列(sequence.cpp/c/pas)
题目来源:原创
考察要点:搜索与剪枝、dancing links、二分、排序
涉及要点:动态规划、随机化算法、贪心
解题报告:
题目可以理解为在串t中选取m个字母,每个字母覆盖串s1和串s2的部分位置,使串s1和串s2被完全覆盖,求满足如上条件时距离的最小值。
对于数据点1,n <= 10,T <= 10,可以直接枚举选取哪m个字母,简单计算即可。
由此可知,对于本题来说,判定比求解来的容易许多。又本题满足答案单调性(即距离为x时有可行解,则距离为x+1时也必有可行解),故可先二分答案,从“求解所有可行解的最小距离x”转化为“距离为x时是否有可行解”的判定问题。这一判定问题实际上就是经典的重复覆盖问题。
对于数据点2,n <= 20,T <= 10,二分答案后,再使用深度优先搜索解决重复覆盖问题。这里有两个优化要点,其一是对所有可能的答案排序后离散化(即ASCII差+距离差是有限的),其二是先随机几组解作为答案的上界。
对于数据点3、4, val[i] = i * 42, T <= 50,因此时相邻的val[]的差42比字符集大小26来得大,由贪心思想易得,子序列中每个字母覆盖的位置必然是连续的一个区间,故可使用动态规划解决。
f[i][p][j1][j2]表示t[1..i]中已选择p个字母,覆盖s1[1..j1]与s2[1..j2]的最小距离。转移略
对于数据点5, val[i] = i * 42, T <= 100;与数据点3、4做法相同,但数据组数较多,需使用前面所述的两个优化要点,或者程序的常数较小。
对于数据点6、7, val[i] = i * i;val[]的差很快就会大于26,这两组数据点给选手的随机化算法(如模拟退火)、搜索算法、启发式算法、贪心算法(如构造配合搜索)发挥空间。
对于所有数据点,n <= 40, 0 < m <= n, 0 <= val[i] <= 2000, T <= 100;出题人使用的是dancing links。该算法实际上仍属于深度优先搜索,包含了最优性剪枝、可能性剪枝、启发式搜索等。故只要选手的程序足够优异,也可以使用其他做法通过所有数据点(标准程序最大数据点花费了0.78s,约时间限制的50%)。
dfs(用于理解题意。。。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<set> #include<ctime> #include<queue> #include<cmath> #include<vector> #include<algorithm> #include<map> #define ll long long #define inf 1000000000 using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int T,n,m,mn; int val[25]; char s[4][25]; bool sel[25]; int dis(int x,int pos) { int ans=inf; for(int i=1;i<=n;i++) if(sel[i]) ans=min(ans,abs(s[x][pos]-s[3][i])+val[abs(pos-i)]); return ans; } int getdis(int x) { int ans=0; for(int i=1;i<=n;i++) ans=max(ans,dis(x,i)); return ans; } void dfs(int x,int k) { if(k>m)return; if(x==n+1) { mn=min(max(getdis(1),getdis(2)),mn); return; } sel[x]=1; dfs(x+1,k+1); sel[x]=0; dfs(x+1,k); } int main() { freopen("sequence.in","r",stdin); freopen("sequence.out","w",stdout); T=read(); for(int test=1;test<=T;test++) { printf("Case #%d: ",test); n=read();m=read(); for(int i=0;i<n;i++) val[i]=read(); for(int i=1;i<=3;i++) scanf("%s",s[i]+1); mn=inf; dfs(1,0); printf("%d\n",mn); } return 0; } |
dancing link:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<set> #include<ctime> #include<queue> #include<cmath> #include<vector> #include<algorithm> #include<map> #define ll long long #define inf 1000000000 using namespace std; ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int T,n,m,top,mid,size,val[105]; char s1[105],s2[105],t[105]; int C[10005],l[10005],r[10005],u[10005],d[10005],a[10005]; int h[105],s[105],dis[3][55][55]; bool vis[105]; void link(int x,int c) { size++; C[size]=c; u[size]=c;d[size]=d[c];s[c]++; u[d[size]]=size;d[u[size]]=size; if(h[x]==-1) { h[x]=l[size]=r[size]=size; } else { l[size]=h[x];r[size]=r[h[x]]; l[r[size]]=size;r[l[size]]=size; } } int f() { int ans=0; for(int i=r[0];i;i=r[i])vis[i]=1; for(int i=r[0];i;i=r[i]) if(vis[i]) { vis[i]=0;ans++; for(int j=u[i];j!=i;j=u[j]) for(int k=r[j];k!=j;k=r[k]) vis[C[k]]=0; } return ans; } void del(int c) { s[C[c]]--; for(int i=u[c];i!=c;i=u[i]) l[r[i]]=l[i],r[l[i]]=r[i]; } void add(int c) { s[C[c]]++; for(int i=d[c];i!=c;i=d[i]) l[r[i]]=r[l[i]]=i; } bool dance(int k) { if(r[0]==0)return 1; if(k+f()>m)return 0; int c=r[0]; for(int i=r[c];i;i=r[i]) if(s[i]<s[c])c=i; for(int i=u[c];i!=c;i=u[i]) { del(i); for(int j=r[i];j!=i;j=r[j])del(j); if(dance(k+1))return 1; for(int j=l[i];j!=i;j=l[j])add(j); add(i); } return 0; } void pre() { top=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { dis[0][i][j]=val[abs(i-j)]+abs(s1[i]-t[j]); dis[1][i][j]=val[abs(i-j)]+abs(s2[i]-t[j]); a[++top]=dis[0][i][j]; a[++top]=dis[1][i][j]; } sort(a+1,a+top+1); top=unique(a+1,a+top+1)-a; } void build() { size=2*n; for(int i=0;i<=2*n;i++) { l[i]=i-1,r[i]=i+1; u[i]=d[i]=i; s[i]=0; } r[2*n]=0;l[0]=2*n; for(int i=1;i<=n;i++)h[i]=-1; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(dis[0][i][j]<=a[mid])link(j,i); if(dis[1][i][j]<=a[mid])link(j,i+n); } } int main() { freopen("sequence.in","r",stdin); freopen("sequence.out","w",stdout); T=read(); for(int tes=1;tes<=T;tes++) { printf("Case #%d: ",tes); n=read();m=read(); for(int i=0;i<n;i++)val[i]=read(); scanf("%s",s1+1); scanf("%s",s2+1); scanf("%s",t+1); pre(); int l=1,r=top; while(l<=r) { mid=(l+r)>>1; build(); if(dance(0))r=mid-1; else l=mid+1; } printf("%d\n",a[l]); } return 0; } |
Problem 2 点对游戏(game.cpp/c/pas)
题目来源:原创
考察要点:树的基于点/边的分治、概率与期望
涉及要点:动态规划、广度优先搜索
解题报告:
对于某位玩家,其占据每个点的概率是相同的(因为点与点之间没有区别)。同样,其占据某对点对的概率也是固定的。不妨设这位玩家最终占据了n个点中的x个点,因为三人轮流选取的顺序固定,所以x可以简单求得。则该玩家占据某对点对的概率可以这么计算:
该玩家从n个点中选取x个点的方案数为C(n,x)。
在这些方案中,选取了某对点对的方案数为C(n-2,x-2)。
与前面分析类似,易得每个方案的概率是相等的,故该玩家占据某对点对的概率为C(n-2,x-2)/C(n,x) = x(x-1)/n/(n-1)。
故问题转换为求距离为某个幸运数的点对对数,将其乘以x(x-1)/n/(n-1)即为答案。
对于数据点1,n <= 1000,bfs即可。
对于数据点2、3、4,第i个幸运数为i,因为幸运数<=10,故可以使用树形动态规划求解。F[x][1..10]表示子树x中与节点x距离为1..10的点数,转移略。
如果在bfs中加入剪枝优化(距离超过最大的幸运数后就不再搜索),亦可通过这三个数据点。
对于数据点5、6、7,n为3的倍数,三人答案相同,方便选手计算。
对于所有数据点,3 <= n <= 50000,m <= 10,幸运数大小 <= n,使用树的基于点/边的分治求解。其中树的基于边的分治需要重建树以防止星形图(数据点7和数据点10)时复杂度退化。另外在不开编译优化的情况下,使用vector等STL存图的程序也容易在这两个数据点超时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> #define ll long long #define inf 1000000000 using namespace std; ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } ll ans; int n,m,cnt,rt,sum,K[15],t[5]; int last[100005]; int f[100005],size[100005],deep[100005],tmp[100005]; int q[100005],top; bool vis[100005]; struct edge{ int to,next; }e[200005]; void insert(int u,int v) { e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt; e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt; } void getrt(int x,int fa) { size[x]=1;f[x]=0; for(int i=last[x];i;i=e[i].next) if(e[i].to!=fa&&!vis[e[i].to]) { getrt(e[i].to,x); size[x]+=size[e[i].to]; f[x]=max(f[x],size[e[i].to]); } f[x]=max(f[x],sum-size[x]); if(f[x]<=f[rt])rt=x; } void getdeep(int x,int fa) { q[++top]=deep[x]; for(int i=last[x];i;i=e[i].next) if(e[i].to!=fa&&!vis[e[i].to]) { deep[e[i].to]=deep[x]+1; getdeep(e[i].to,x); } } void solve(int x) { vis[x]=1; tmp[0]=1; int mx=0; for(int i=last[x];i;i=e[i].next) if(!vis[e[i].to]) { deep[e[i].to]=1;top=0; getdeep(e[i].to,x); for(int k=1;k<=m;k++) for(int j=1;j<=top;j++) if(q[j]<=K[k]) ans+=tmp[K[k]-q[j]]; for(int j=1;j<=top;j++) { tmp[q[j]]++; mx=max(mx,q[j]); } } for(int i=0;i<=mx;i++) tmp[i]=0; for(int i=last[x];i;i=e[i].next) if(!vis[e[i].to]) { sum=size[e[i].to]; rt=0; getrt(e[i].to,x); solve(rt); } } void print(double t) { double res=0; res=(double)ans*t/n*(t-1)/(n-1); printf("%.2lf\n",res); } int main() { freopen("game.in","r",stdin); freopen("game.out","w",stdout); n=read();m=read(); for(int i=1;i<=m;i++)K[i]=read(); for(int i=1;i<n;i++) { int u=read(),v=read(); insert(u,v); } f[0]=inf;sum=n; getrt(1,0); solve(rt); t[1]=t[2]=t[3]=n/3; for(int i=1;i<=n%3;i++)t[i]++; for(int i=1;i<=3;i++)print(t[i]); return 0; } |
黄学长能不能把day1开始的全部发一下,不胜感激
orz
%%%%%
第3题呢?