省选模拟测试13

期望得分:\(100+40+0 = 140\)

实际得分:\(30+40+0=70\)

\(T1\) 巨型分类讨论的构造题,自己少考虑一种情况,只拿了一个 \(subtack\) 的分。

\(T2\) 比较难的转化题,主要是我不怎么会仙人掌,考试的时候只打了树的暴力分。

\(T3\) 题目都没读懂,暴力也就没打明白。

T1 华灵-蝶妄想 (butterfly)

题意描述

给定一个 \(n∗m\) 大小的网格, 每个格子里可以填 ′(′ 或 ′)′. 某一行或某一列可以形成一个合法的括号序列.问最大有多少行和多少列是合法的括号序列.

数据范围:\(n,m\leq 5000\)

solution

巨型分类讨论构造题。

首先因为合法的括号序只能为偶数,所以我们要分奇偶讨论一下:

  • \(n\) 为奇数

这种情况很简单,只需要让每一列都合法即可,答案为 \(m\), 构造方案如下:

//n=3, m = 4 的情况
(((
(((
)))
)))    
  • \(m\) 为奇数的时候

和第一种情况一下,只不过我们需要让每一行合法,答案为 \(n\), 构造方案如下:

//n = 4, m = 3 的情况
()()
()()
()()
  • \(n,m\) 都为偶数的时候

这种情况就比较复杂了,我们有三种构造方案。

方案1: 答案为 \(n+m-4\)

我们考虑舍弃第一行和最后一行,中间的行构造:

//n = 6, m = 6 的情况
((((((
(()())
()()()
(()())
()()()
))))))

不难发现这样其实是 \(n+m-4\)

方案2:答案为 \(n/2+m-1\)

\(n\) 比较小的时候,我们可以考虑牺牲一半的行来这样构造

// n = 4, m = 6
((((((
()()()
)()()(
))))))

方案3:答案为:\(n+m/2-1\)

这个其实和方案2的思路是一样的,就是把上面那个倒过来一下就可以了。

//n = 6,, m = 4
((((
()()
)()(
()()
)()(
))))

所以当 \(n,m\) 都为偶数的时候,我们取这三种方案的最大值就可以了。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
char s[5010][5010];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
int main()
{
	freopen("butterfly.in","r",stdin);
	freopen("butterfly.out","w",stdout);
    n = read(); m = read();
    if((n & 1) && (m & 1))
    {
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                cout<<'(';
            }
            cout<<endl;
        }
    }
    else if(n & 1)
    {
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m/2; j++) cout<<'(';
            for(int j = m/2+1; j <= m; j++) cout<<')';
            cout<<endl;
        }
    }
    else if(m & 1)
    {
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n/2; j++) s[j][i] = '(';
            for(int j = n/2+1; j <= n; j++) s[j][i] = ')';
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                cout<<s[i][j];
            }
            cout<<endl;
        }
    }
    else
    {
    	int t1 = n+m-4, t2 = n/2+m-1, t3 = n+m/2-1;
    	int maxn = max(t1,max(t2,t3));
    	if(maxn == t1)
    	{
    		for(int i = 1; i <= m; i++) cout<<'(';
    		cout<<endl;
    		for(int i = 2; i <= n-1; i++)
    		{
    			if(i&1) for(int j = 1; j <= m; j++) (j & 1) ? cout<<'(' : cout<<')';
    			else
			{
				cout<<'('; 
				for(int j = 2; j <= m-1; j++) !(j & 1) ? cout<<'(' : cout<<')'; 
				cout<<')';		
                                cout<<endl;
			}  
			for(int i = 1; i <= m; i++) cout<<')';
			cout<<endl;
		}
		else if(maxn == t2)
		{
			for(int i = 1; i <= m; i++) cout<<'(';
			cout<<endl;
			for(int i = 2; i <= n-1; i++)
			{
				if(i & 1) for(int j = 1; j <= m; j++) (j&1) ? cout<<'(' : cout<<')';
				else for(int j = 1; j <= m; j++) !(j&1) ? cout<<'(' : cout<<')';
				cout<<endl;
			}
			for(int i = 1; i <= m; i++) cout<<')';
			cout<<endl;
		}
		else if(maxn == t3)
		{
			for(int i = 1; i <= n; i++) s[i][1] = '(';
			for(int i = 1; i <= n; i++) s[i][m] = ')';
			for(int i = 2; i <= m-1; i++)
			{
				if((i & 1) == 0) for(int j = 1; j <= n; j++) s[j][i] = (j&1) ? ')' : '(';
				else for(int j = 1; j <= n; j++) s[j][i] = (j&1) ? '(' : ')';
			}
			for(int i = 1; i <= n; i++)
			{
				for(int j = 1; j <= m; j++) cout<<s[i][j];
				cout<<endl;
			}
		} 
    }
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 樱符-完全墨染的樱花 (sakura)

题意描述

给你一个 \(n\) 个点 \(m\) 条边的无向图,定义一个图的价值为 \(\displaystyle\sum_{s=1}^{n}\sum_{t=1}^{n} maxflow(s,t)\times p^{(s-1)\times n+t}\)\(maxflow(s,t)\) 即为 \(s,t\) 之间的最大流。

一开始图上的边权全为 \(1\), 且 \(maxflow(s,t)\leq 2\), 现在将第 \(i\) 条边的边权改为了 \(c[i]\), 问你修改后图的价值最大是多少。

数据范围:\(n\leq 3\times 10^5,m\leq 5\times 10^5\)

solution

并查集加构造转化

原图其实有个特殊性质 \(maxflow(s,t)\leq 2\) ,这就表示一条边只会在一个环上。

因为如果一条边在两个环上的话,显然最大流为 \(3\)

也就表明这个图其实就是个仙人掌。

考虑这个仙人掌图上的最小割(最小割等于最大流)。

割边要么是一条桥边,要么是同时割掉环上的两条边。考虑当割掉环上的两条边的时候,无论怎么样边权最小的肯定会被割掉。

因此我们可以找到每个环上边权最小的边,然后把环上的其他边的边权加上边权最小的边的边权,然后在把边权最小的边断掉,不难发现这样我们这样构造出来的最大流和原图的最大流是一样的。

由于我们把每个环都断掉了一条边,显然构造出来的图是一棵树。

我们考虑把树上的每一条边从大到小排序,依次枚举每一条边,合并左右端点所在的联通块,不难发现从左端点所在的联通块中选择一个起点,在从右端点所在联通块中选一个终点(反过来也可以),最大流一定是当前枚举这条边的边权。因此可以那并查集来维护一下每个联通块的 \(\sum p^{{(i-1})\times n}\)\(\sum p^{i}\) 即可。

感觉这个题难点就在这个转化上,树的部分其实很好想的。

一个小技巧:仙人掌图找环的时候,可以先建一个树出来,然后枚举所有非树边,边的两个端点树上路径的边就是环上的其他边。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define LL long long
const int inf = 1e9+10;
const int mod = 998244353;
const int N = 3e5+10;
int n,m,p,s,t,u,v,w,tot = 1;
int head[N],dep[N],fa[N],siz[N],f[N],id[N];
LL sum1[N],sum2[N];
bool can[N],used[N];
struct bian
{
	int u,v,w,tag;
}q[N<<1];
struct node
{
	int to,net,w,id;
}e[N<<1];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
bool comp(bian a,bian b){return a.w > b.w;}
void add(int x,int y,int id)
{
	e[++tot].to = y;
	e[tot].id = id;
	e[tot].net = head[x];
	head[x] = tot;
}
LL ksm(LL a,LL b)
{
	LL res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = 1LL * res * a % mod;
		a = 1LL * a * a % mod;
	}
	return res;
}
int find(int x)
{
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}
void slove()
{
	LL ans = 0, tmp = ksm(p,n);
	sort(q+1,q+m+1,comp);
	fa[1] = 1; sum1[1] = 1; sum2[1] = p;
	for(int i = 2; i <= n; i++)
	{
		fa[i] = i;
		sum1[i] = 1LL * sum1[i-1] * tmp % mod;
		sum2[i] = 1LL * sum2[i-1] * p % mod;
	}
	for(int i = 1; i <= m; i++)
	{
		int fx = find(q[i].u), fy = find(q[i].v);
		if(fx == fy || q[i].tag) continue;
		ans = (ans + 1LL * q[i].w * sum1[fx] % mod * sum2[fy] % mod) % mod;
		ans = (ans + 1LL * q[i].w * sum1[fy] % mod * sum2[fx] % mod) % mod;
		fa[fx] = fy;
		sum1[fy] = (sum1[fy] + sum1[fx]) % mod;
		sum2[fy] = (sum2[fy] + sum2[fx]) % mod;
	}
	printf("%lld\n",ans);
}
void dfs(int x,int fa)
{
	f[x] = fa; dep[x] = dep[fa] + 1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa) continue;
		id[to] = e[i].id;
		dfs(to,x);
	}
}
int main()
{
	freopen("sakura.in","r",stdin);
	freopen("sakura.out","w",stdout);
	n = read(); m = read(); p = read();
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++)
	{
		q[i].u = read(), q[i].v = read(), q[i].w = read();
		int fx = find(q[i].u), fy = find(q[i].v);
		if(fx == fy) continue;
		fa[fx] = fy; used[i] = 1;
		add(q[i].u,q[i].v,i); add(q[i].v,q[i].u,i);
	}
	dfs(1,0);
	for(int i = 1; i <= m; i++)
	{
		if(!used[i])
		{
		        int x = q[i].u, y = q[i].v;
			int minn = q[i].w, tmp = i;
			if(dep[x] < dep[y]) swap(x,y);
			while(dep[x] > dep[y])
			{
				if(minn > q[id[x]].w) tmp = id[x], minn = q[id[x]].w;
				x = f[x];
			}
			while(x != y)
			{
				if(minn > q[id[x]].w) tmp = id[x], minn = q[id[x]].w;
				if(minn > q[id[y]].w) tmp = id[y], minn = q[id[y]].w;
				x = f[x]; y = f[y];
			}
			q[tmp].tag = 1; x = q[i].u; y = q[i].v;
			if(tmp != i) q[i].w += minn;
			if(dep[x] < dep[y]) swap(x,y);
			while(dep[x] > dep[y])
			{
				if(id[x] != tmp) q[id[x]].w += minn;
				x = f[x];
			}
			while(x != y)
			{
				if(id[x] != tmp) q[id[x]].w += minn;
				if(id[y] != tmp) q[id[y]].w += minn; 
				x = f[x]; y = f[y];
			}
		}
	}
	slove();
	return 0;
}

T3 幽曲-埋骨于弘川 (buried)

题意描述

给定一个 \(n\) 个节点的树,其中 \(1\) 为根节点,每个点有点权,我们定义“子树”为若干条到根的链的并。给定一个无穷数列 \({a_n}\) ,其构造方法如下:

\(a_n =\begin{cases}1&x = 1\\a[n-1] + maxDight(a[n-1]&x>1\end{cases}\)

其中 \(maxDigit(n)\) 函数为 \(n\) 的最大数位。

这个无穷数列的前 \(6\) 项为 \(1,2,4,8,16,22⋯\) ,求原树有多少个子树先序遍历得到的权值序列包含在 \({a_n}\) 中。

数据范围:\(\sum n\leq 500\)

solution

神仙 \(dp\)\(dp\) 题,咕咕咕。

posted @ 2021-03-16 07:17  genshy  阅读(95)  评论(1编辑  收藏  举报