省选模拟测试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\) 题,咕咕咕。