CF1926F. Vlad and Avoiding X
题意:
给定一个
7
∗
7
7*7
7∗7的网格,网格上的点不是黑色就是白色,要求修改最少的点,使得网格中没有X形状的黑色网格。
思路:
首先看到这个数据范围,很容易想到暴搜,但是总共有
7
∗
7
=
49
7*7=49
7∗7=49个网格,如果暴力dfs去搜的话,时间复杂度就是
O
(
2
49
)
O(2^{49})
O(249),一定是会超时的,因此我们看如何优化,会发现,坐标和为奇数与偶数的格子是互不影响的,也就是所有可能的X形状的格子,要么坐标和全是奇数,要么全是偶数,即下图中,X形状的格子要么全是蓝色,要么全是棕色
那么我们就可以进行两次搜索,一次搜索坐标和为奇数的格子,另一次搜索坐标和为偶数的格子,时间复杂度就优化成了
O
(
2
24
+
2
25
)
O(2^{24}+2^{25})
O(224+225),我们再观察一下,如果要修改最外圈的点,那么我们总能找到修改内圈中的点来替换最外圈的点来达到更优的效果,那么时间复杂度就来到了
(
O
(
2
12
+
2
13
)
(O(2^{12}+2^{13})
(O(212+213),这样就可以通过了本题了,同时可以加一个最优性剪枝,进一步优化时间复杂度,在我们搜索的时候其实会有很多点会重复搜索,因此我们每次可以少搜两个点,具体哪两个点可以看你的代码怎么写了
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
void solve()
{
string s[7];
for(int i=0;i<7;i++)cin>>s[i];
int ans=0;
for(int p=0;p<2;p++)//分别统计坐标和为奇数或偶数的情况
{
int res=100;
auto dfs = [&](auto dfs,int x,int y,int cnt)
{
if(cnt>=res)return;//剪枝
if(x==5)//边界情况
{
res=min(res,cnt);
return;
}
if(y==5)//到达边界,x+1,y=0
{
dfs(dfs,x+1,0,cnt);
return;
}
if((x+y)%2!=p)//坐标和要跟当前遍历的p相等
{
dfs(dfs,x,y+1,cnt);
return;
}
if(s[x][y]=='B'&&s[x][y+2]=='B'&&s[x+1][y+1]=='B'&&s[x+2][y]=='B'&&s[x+2][y+2]=='B')
{
// s[x][y]='W';
// dfs(dfs,x,y+1,cnt+1);//这个点会重复修改可以不要
// s[x][y]='B';
// s[x][y+2]='W';
// dfs(dfs,x,y+1,cnt+1);//位于最外一圈的点可以不修改,因为总能找到内层的点修改,会更优
// s[x][y+2]='B';
s[x+1][y+1]='W';
dfs(dfs,x,y+1,cnt+1);
s[x+1][y+1]='B';
s[x+2][y]='W';
dfs(dfs,x,y+1,cnt+1);
s[x+2][y]='B';
s[x+2][y+2]='W';
dfs(dfs,x,y+1,cnt+1);
s[x+2][y+2]='B';
}
else dfs(dfs,x,y+1,cnt);
};
dfs(dfs,0,0,0);
ans+=res;
}
cout<<ans<<"\n";
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
solve();
}
return 0;
}
CF1926G. Vlad and Trouble at MIT
题意:
给定一棵树,树上的点要么是P,要么是S,要么是C,C可以替换成P,也可以替换成C,要求在树中加一些墙,使得任意的P跟S之间都是有墙的。
思路:
如果这个题没有C的话,我们可以用并查集缩点然后建图,统计每个S点的度数即可。但是有C那我们应该怎么做呢?
我们令 d p [ u ] [ 0 / 1 ] dp[u][0/1] dp[u][0/1]表示当前点u为S/P时,以点u为根的子树中满足题意需要加墙的最小操作数。如果当前点u是C,那么就可以变成S或P,此时 d p [ u ] [ 0 ] = d p [ u ] [ 1 ] = 0 dp[u][0]=dp[u][1]=0 dp[u][0]=dp[u][1]=0否则不能,如果当前点为S,那么 d p [ u ] [ 0 ] = I N F dp[u][0]=INF dp[u][0]=INF(极大值),那么只需要将当前点的儿子中P分割开,加一堵墙即可。反之亦然。
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10 ,INF = 1e9;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
int n;cin>>n;
vector<vector<int>>g(n+1);
for(int i=2;i<=n;i++)
{
int x;cin>>x;
g[x].push_back(i);//由题意,可以只建一条边
}
string s;cin>>s;s=" "+s;
vector<vector<int>>dp(n+1,vector<int>(2,0));
//dp[u][0]表示当前点为S时,以点u为根的子树满足题意的最小方案数
//dp[u][1]表示当前点为P时,以点u为根的子树满足题意的最小方案数
auto dfs = [&] (auto dfs,int u)->void
{
dp[u][0]=dp[u][1]=0;//当前点s[u]=C时,dp[u][0]=dp[u][1]=0
if(s[u]=='P')dp[u][0]=INF;//当前点s[u]如果为P,那么就不可能为S
if(s[u]=='S')dp[u][1]=INF;//当前点s[u]如果为S,那么就不可能为P
for(auto j:g[u])
{
dfs(dfs,j);
dp[u][0]+=min(dp[j][0],dp[j][1]+1);//如果儿子与当前点相同,就不用加隔板,否则要加一个隔板
dp[u][1]+=min(dp[j][1],dp[j][0]+1);
}
};
dfs(dfs,1);
cout<<min(dp[1][0],dp[1][1])<<"\n";//答案就是两种情况的最小值
}
return 0;
}