Atcoder Grand Contest 024

A

B

C

D(构造分形)

题意:

  给出一个由n个点的组成的树,你可以加一些点形成一个更大的树。对于新树中的两个点i和j,如果以i为根的树与以j为根的树是同构的那么i和j颜色可以相同。问最少需要多少颜色,在颜色最少的情况下,最少需要多少叶子节点。

  n<=100

分析:

  根据给的样例画一画,就明白是需要把树补成一个“分形”的结构,那么离分形中心距离一样的点就是同颜色的,于是我们希望最小化离中心最大的点的距离

  也就是说最少颜色一定是树的直径的一半,于是我们自然想到把直径拉出来,取中间的那个点为分形中心

  但良心的样例告诉我们,这样取不一定会让叶子节点的个数最少,你可以把一条边作为分形中心,分成左右两个分形,而且这个边可能也不在直径上

  考虑到n<=100,我们不妨枚举哪个点作为中心,枚举哪个边作为中心,去取一个最小值即可

  时间复杂度O(n^2)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=100;
 4 vector<int> g[maxn+5];
 5 int n,s,t;
 6 int fa[maxn+5],dep[maxn+5],a[maxn+5];
 7 long long ans;
 8 void dfs(int k,int last)
 9 {
10     dep[k]=dep[last]+1;
11     fa[k]=last;
12     int d=0;
13     for(auto u:g[k])
14     {
15         if(u==last) continue;
16         dfs(u,k);
17         ++d;
18     }
19     a[dep[k]]=max(a[dep[k]],d);
20 }
21 int main()
22 {
23     scanf("%d",&n);
24     for(int i=1;i<n;++i)
25     {
26         int u,v;
27         scanf("%d%d",&u,&v);
28         g[u].push_back(v),g[v].push_back(u);
29     }
30     dfs(1,0);
31     for(int i=1;i<=n;++i)
32         if(dep[i]>dep[s]) s=i;
33     dfs(s,0);
34     for(int i=1;i<=n;++i)
35         if(dep[i]>dep[t]) t=i;
36     //printf("%d %d\n",s,t);
37     int ans1=(dep[t]+1)/2;
38     printf("%d ",ans1);
39     for(int i=1;i<=n;++i)
40     {
41         memset(a,0,sizeof(a));
42         a[0]=1;
43         dfs(i,0);
44         long long res=1;
45         for(int j=0;j<=n;++j)
46             if(a[j]==0) break;else res=res*a[j];
47         if(a[ans1]!=0) continue;
48         if(ans==0||res<ans) ans=res;
49     }
50     for(int u=1;u<=n;++u)
51         for(auto v:g[u])
52         {
53             memset(a,0,sizeof(a));
54             a[0]=2;
55             dep[u]=0;
56             dfs(v,u);
57             dep[v]=0;
58             dfs(u,v);
59             long long res=1;
60             for(int j=0;j<=n;++j)
61                 if(a[j]==0) break;else res=res*a[j];
62             if(a[ans1]!=0) continue;
63             if(ans==0||res<ans) ans=res;
64         }
65     printf("%lld\n",ans);
66     return 0;
67 }
View Code

E(计数)

题意:

  给定一个n和k,我们构造一组A0,A1,...,An

  其中Ai是一个有i个元素的数列,每个数的范围是1~k

  若Ai-1是Ai的子序列且字典序满足Ai>Ai-1,则我们称这一组A是合法的,问一共有多少种合法的A,答案对M取模。

  n,k<=300,m<=1e9

分析:

  我们考虑第i次操作,加入一个编号为i的点,这个点的权值就是Ai中多加的数字x,把其放到Ai-1中哪个位置的前面,就把这个点的父亲连到那个点

  然后我们考虑字典序限制,x必须放到一个比x小的数字前面(如果放到相同的前面,实际上等价于放在连续段的最后一个)

  于是就变成了一个有n+1个节点的树,然后每个点的权值都比孩子的权值大,每个点的编号都比孩子的编号小,一个树和一个A是一一对应的,于是我们对这个树计数就行了

  dp[i][j]表示有i个点的树,root的权值是j情况下的方案数,那怎么转移呢?

  我们去枚举1号点所在的子树的节点个数和1号点的权值去转移

  这样是四次方的,但写出式子发现可以前缀和优化,于是就是O(n^3)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=300;
 4 typedef long long ll;
 5 int n,m,mod;
 6 ll dp[maxn+5][maxn+5],sum[maxn+5][maxn+5],c[maxn+5][maxn+5];
 7 void work(ll &a,ll b)
 8 {
 9     a=(a+b)%mod;
10     if(a<0) a+=mod;
11 }
12 int main()
13 {
14     scanf("%d%d%d",&n,&m,&mod);
15     c[0][0]=1;
16     for(int i=1;i<=n+1;++i)
17     {
18         c[i][0]=1;
19         for(int j=1;j<=i;++j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
20     }
21     for(int i=0;i<=m;++i) dp[1][i]=1;
22     sum[1][0]=1;
23     for(int i=1;i<=m;++i) sum[1][i]=(sum[1][i-1]+dp[1][i])%mod;
24     for(int i=2;i<=n+1;++i)
25     {
26         for(int j=0;j<=m;++j)
27             for(int k=1;k<i;++k)
28                 work(dp[i][j],dp[i-k][j]*c[i-2][k-1]%mod*(sum[k][m]-sum[k][j])%mod);
29         sum[i][0]=dp[i][0];
30         for(int j=1;j<=m;++j) sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
31     }
32     printf("%lld\n",dp[n+1][0]);
33     return 0;
34 }
View Code

F(DAG图dp)

题意:

  给定一些01字符串 ,现在你找一个01字符串s,如果给定的这些01字符串里至少有m个字符串包含s作为子序列,那么s就是合法的。对于所有合法的s,找到长度最长的(在这基础上找字典序最小的)

  01字符串的给定方式见题面

分析:

  如果我们可以求出长度<=n的所有字符串被多少个给定字符串包含作为子序列,那么这个问题就能轻松解决了

  我们如何描述一个字符串的所有子序列呢?

  我们用s[t]表示已经固定了s,然后取t中的子序列

  那么s[t]可以转移到s0[t']  s1[t']

  并且这个dag有一个性质,就是任意两个点的路径个数<=1

  所以就可以在这个dag图上进行dp

  时间复杂度O(2^n*n^2)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=20;
 4 int dp[2][20+1][1<<maxn],nx[20+1][1<<maxn][2];
 5 char s[1<<maxn];
 6 int n,m,ans,len;
 7 int main()
 8 {
 9     scanf("%d%d",&n,&m);
10     int now=0;
11     for(int i=0;i<=n;++i)
12     {
13         scanf("%s",s);
14         for(int j=0;j<(1<<i);++j)
15             if(s[j]=='1') dp[now][i][j]=1;
16     }
17     for(int i=1;i<=n;++i)
18         for(int j=0;j<(1<<i);++j)
19         {
20             nx[i][j][0]=nx[i][j][1]=-1;
21             for(int k=i-1;k>=0;--k)
22                 if((j>>k)&1)
23                 {
24                     nx[i][j][1]=k;
25                     break;
26                 }
27             for(int k=i-1;k>=0;--k)
28                 if(((j>>k)&1)==0)
29                 {
30                     nx[i][j][0]=k;
31                     break;
32                 }
33         }
34     for(int i=0;i<n;++i)
35     {
36         for(int j=n-i;j>=1;--j)
37             for(int s=(1<<(i+j))-1;s>=0;--s)
38             {
39                 if(!dp[now][i+j][s]) continue;
40                 int t=nx[j][s&((1<<j)-1)][0];
41                 if(t!=-1)
42                     dp[now^1][i+t+1][(s>>j<<t+1)|(s&(1<<(t+1))-1)]+=dp[now][i+j][s];
43                 t=nx[j][s&((1<<j)-1)][1];
44                 if(t!=-1)
45                     dp[now^1][i+t+1][(s>>j<<t+1)|(s&(1<<(t+1))-1)]+=dp[now][i+j][s];
46             }
47         memset(dp[now],0,sizeof(dp[now]));
48         now^=1;
49         for(int j=1;i+1+j<=n;++j)
50             for(int s=0;s<1<<(i+1+j);++s)
51                 dp[now][i+1][s>>j]+=dp[now][i+1+j][s];
52         for(int s=(1<<(i+1))-1;s>=0;--s)
53         {
54             if(dp[now][i+1][s]>=m) len=i+1,ans=s;
55         }
56     }
57     for(int i=len-1;i>=0;--i)
58         if(ans&(1<<i)) printf("1");else printf("0");
59     return 0;
60 }
View Code

猜你喜欢

转载自www.cnblogs.com/wmrv587/p/9084974.html