2022 纪中集训 7.8
A组
T1 数字八
考虑动态规划。
\(f[k][i][j]\) 为一第 \(k\) 行为底,底边两端点为 \(i, j\) 的,向上的最大矩形大小。
\(g[k][i][j]\) 为一第 \(k\) 行为顶,底边两端点为 \(i, j\) 的,向下的最大矩形大小。
可以用前缀 *
和来判断 \(i, j\) 内是否有 *
。
可得转移方程:
- 当端点 \(i, j\) 上有
*
, 则 \(f[k][i][j]\) 为 \(0\)- 当 \([i, j]\) 中没有
*
,设上一个空行的位置为 \(t\) ,则 \(f[k][i][j] = max\{(k-t-1) \times (j-i-1), f[k][i+1][j], f[k][i][j-1] \}\)
同理可处理 \(g[k][i][j]\)。
需要注意的是,单是 \(f\) 数组就占了 \(102MB\), 加上 \(g\) 数组就肯定 \(MLE\)。
所以我们选择记录 \(f\),g 直接乘在 \(f\) 里面就可以了。
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
const int INF = 0x3f3f3f3f;
int n;
char s[N][N];
int sum[N][N];
int f[N][N][N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
sum[i][j] = sum[i][j-1] + (s[i][j] == '*');
for(int i=1;i<=n-2;i++)
for(int j=i+2;j<=n;j++)
{
int t = 0;
for(int k=1;k<=n;k++)
{
if(s[k][i] == '*' || s[k][j] == '*') t = 0;
if(sum[k][j] - sum[k][i-1] == 0)
if(!t) t = k;
else f[k][i][j] = (k - t - 1) * (j - i - 1);
}
}
for(int k=1;k<=n;k++)
{
for(int j=n;j>=3;j--)
for(int i=j-3;i>=0;i--)
if(sum[k][j] - sum[k][i-1] == 0)
f[k][i][j] = max(f[k][i][j], f[k][i+1][j]);
for(int i=1;i<=n-2;i++)
for(int j=i+2;j<=n;j++)
if(sum[k][j] - sum[k][i-1] == 0)
f[k][i][j] = max(f[k][i][j], f[k][i][j-1]);
}
int ans = 0;
for(int i=1;i<=n-2;i++)
for(int j=i+2;j<=n;j++)
{
int t = 0;
for(int k=n;k>=0;k--)
{
if(s[k][i] == '*' || s[k][j] == '*') t = 0;
if(sum[k][j] - sum[k][i-1] == 0)
if(!t) t = k;
else ans = max(ans,f[k][i][j] * (t - k - 1) * (j - i - 1));
}
}
if(!ans) cout<<-1<<endl;
else cout<<ans<<endl;
return 0;
}
T3 毒奶
将现实城市记作黑点,梦幻城市记作白点。
先缩点,将可以可以用道路相同的点缩为一个连通块来表示,可以用并查集实现。
每个对应方法本质上就是一种从黑块连 \(n\) 条边到白块,最后使得整个图联通的方案。
随意选一个白块为根,连边一定为黑到白到黑。
然后考虑状态压缩DP。
设 \(f[S][i][c]\) 为选了的块集为 \(S\), 伸向下一层的边数为 \(i\), 当前颜色为 \(c\) 的方案数。
枚举 \(S\) 的子集 \(t\),记 \(t\) 中的元素个数为 \(q\) , t中联通块的点数和为 \(p\)
可得转移为:
\(f[s][p-q][c] += f[s-t][q][!c] * A_q^q\)
由于联通块中的每一个点都可以充当与父亲块之间的连接点,所以最后答案要乘上所有块的 \(size\) 之积。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1050000, M = 20 * 2;
const int Mod = 998244353;
struct Node
{
int u, v;
}e[M];
int n, m;
int fa[N], siz[N], siz1[N];
int find(int x) { return x != fa[x] ? fa[x] = find(fa[x]) : x; }
void unionn(int r1, int r2) { fa[find(r1)] = find(r2); }
LL fac[N];
int f[N][2][25];
int cnt[N], tot[N];
int main()
{
freopen("milk.in","r",stdin);
freopen("milk.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=2*n;i++) fa[i] = i;
for(int i=1;i<=m;i++) scanf("%d%d",&e[i].u,&e[i].v);
for(int i=m+1;i<n;i++) scanf("%d%d",&e[i].u,&e[i].v), e[i].u += n ,e[i].v += n;
fac[0] = 1;
for(int i=1;i<=n;i++) fac[i] = fac[i-1] * i % Mod;
for(int i=1;i<n;i++) unionn(e[i].u,e[i].v);
for(int i=1;i<=2*n;i++) siz[find(i)] ++;
int cnt1 = 0;
for(int i=1;i<=n;i++)
if(find(i) == i)
siz1[++cnt1] = siz[i];
m = cnt1;
for(int i=n+1;i<=2*n;i++)
if(find(i) == i)
siz1[++cnt1] = siz[i];
n = cnt1;
f[0][1][siz1[n]] = 1; n --;
int p[2];
p[0] = (1<<m)-1, p[1] = ((1<<n)-1) ^ p[0];
for(int s=0;s<(1<<n);s++)
for(int i=0;i<n;i++)
if(s>>i&1)
cnt[s]++, tot[s] += siz1[i+1];
for(int s=0;s<(1<<n);s++)
{
int u[2];
u[0] = s & p[0], u[1] = s & p[1];
for(int c=0;c<=1;c++)
{
if(s != (1 << n) - 1 && u[c^1] == p[c^1]) continue;
for(int t=u[c];t;t=(t-1)&u[c])
if(f[s^t][c^1][cnt[t]])
f[s][c][tot[t] - cnt[t]] = (f[s][c][tot[t] - cnt[t]] + f[s^t][c^1][cnt[t]] * fac[cnt[t]]) % Mod;
}
}
LL ans = (f[(1<<n)-1][0][0] + f[(1<<n)-1][1][0]) % Mod;
for(int i=1;i<=n;i++) ans = siz1[i] * ans % Mod;
cout<<ans<<endl;
return 0;
}