2022 纪中集训 7.8

A组

T1 数字八

image

考虑动态规划。
\(f[k][i][j]\) 为一第 \(k\) 行为底,底边两端点为 \(i, j\) 的,向上的最大矩形大小。

\(g[k][i][j]\) 为一第 \(k\) 行为顶,底边两端点为 \(i, j\) 的,向下的最大矩形大小。

可以用前缀 * 和来判断 \(i, j\) 内是否有 *

可得转移方程:

  1. 当端点 \(i, j\) 上有 *, 则 \(f[k][i][j]\)\(0\)
  2. \([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 毒奶

image

将现实城市记作黑点,梦幻城市记作白点。

先缩点,将可以可以用道路相同的点缩为一个连通块来表示,可以用并查集实现。

每个对应方法本质上就是一种从黑块连 \(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;
}
posted @ 2022-07-08 15:05  BorisDimitri  阅读(23)  评论(0编辑  收藏  举报