补题:
C - Matrix Reducing
暴力:
题意:给定矩阵A、B,问是否可以通过删除某行某列使A矩阵=B矩阵;输出Yes,No;
解题:
-
首先找到与B矩阵匹配的第一个点;
-
找到开始的行列,开始匹配
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=19;
int a[N][N],b[N][N];
int h1,w1,h2,w2;
bool solve(int r,int c)
{
vector<int>ch;
int cnt=0,idx=0;
for(int i=c;i<w1;i++)
{
if(a[r][i]==b[0][idx])
{
ch.push_back(i);
idx++;
}
}//找到符合第一行的,满足条件的列;
if(idx!=w2)
return false;//找不到则直接返回false;
idx=0;//再重新从行中找;
for(int i=r;i<h1;i++)
{
int count=0;//要求每一行找到匹配的w2列
for(int j=0;j<w2;j++)
{
if(a[i][ch[j]]==b[idx][j])//枚举矩阵A中的列时选择符合第一行的列;
{
count++;
}
}
if(count==w2)
idx++;
}
if(idx!=h2)
return false;
return true;
}
int main()
{
cin>>h1>>w1;
for(int i=0;i<h1;i++)
for(int j=0;j<w1;j++)
cin>>a[i][j];
cin>>h2>>w2;
for(int i=0;i<h2;i++)
for(int j=0;j<w2;j++)
cin>>b[i][j];
//首先找到符合的第一个
for(int i=0;i<h1;i++)
for(int j=0;j<w1;j++)
{
if(a[i][j]==b[0][0])
{
if(solve(i,j))
{
cout<<"Yes"<<endl;
return 0;
}
}
}
cout<<"No"<<endl;
return 0;
}
E - Blackout 2
并查集+超级源点
题意:n个城市、m个发电站现在有e条边,连接两个地点(任意两个地点,可以是发电站、城市);
1-n为城市、n+1->n+m的点为供电站,城市能实现供电的条件是至少连了一个发电站。
现在有q次操作,每次操作删掉e条边中的一条,问:每次操作之后,需要统计这些边到达某个发电站的城市个数;注意:每个操作永久有效;
解题:从最终状态考虑,先删去所有边,再加边;倒序枚举存答案;注意:输出;
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N=1e6+10;
int p[N],cnt[N];//父节点,答案;cnt【0】;
bool st[N];//标识删除的边
int query[N];//存需要删除的边
PII g[N];//存地图
int find(int x)
{
if(p[x]!=x)
p[x] =find(p[x]);
return p[x];
}//并查集
int main()
{
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
p[i]=i;
cnt[i]=1;
}
for(int i=n+1;i<=n+m;i++)
{
p[i]=0;
}
for(int i=1;i<=k;i++)
{
int u,v;
cin>>u>>v;
g[i].first=u;
g[i].second=v;
}
int q;
cin>>q;
for(int i=1;i<=q;i++)
{
int x;
cin>>x;
st[x]=true;//保存需要删除的边 ,第x条线损坏 删除
query[i]=x;
}
for(int i=1;i<=k;i++)//进行初始加边操作
{
int a=g[i].first;
int b=g[i].second;
if(!st[i])//如果一直不被删除,则加边
{
a=find(a),b=find(b);
if(a!=b)
{
if(a==0)
swap(a,b);
p[a]=b;
cnt[b]+=cnt[a];
}
}
}
//按删除倒序进行加边
vector<int>ans;//存答案
for(int i=q;i>=1;i--)
{
int x=query[i];//存需要删除的边
int a=g[x].first;
int b=g[x].second;//存两边
ans.push_back(cnt[0]);//已删除时的答案
a=find(a),b=find(b);//存完后进行加边,
if(a!=b)
{
if(a==0)
swap(a,b);
p[a]=b;
cnt[b]+=cnt[a];
}
}
reverse(ans.begin(),ans.end());//答案翻转;因为倒着求的
for(auto s:ans)
cout<<s<<endl;
}
F - Monochromatic Path
dp
题意 :给定一个h*w的01矩阵,0代表白色,1代表黑色,现在给你两个操作:1、使得某一行(i行)颜色翻转代价为r(i);2、使得某一列颜色翻转(j列)颜色翻转,代价c(j);
问:最少操作几次使得从(1,1)到(h,w)存在一条路径,这条路径的颜色相同;
解题:对于每一行操作2次是无效的,且代价增大;所以,最优解对于每一行或每一列的操作次数只可能是0/1;
并且只能向下或者向右走;
dp解决:
四维数组,dp(i,j,0/1,0/1):表示走到(i,j)这个格子,对第i行操作了0/1次,对第j列操作了0/1次;
考虑状态转移:选择从(i,j)转移到(i+1,j)和(i,j+1);这样选择只需比较a(i,j)和a(i+1,j)、a(i,j+1)在操作影响下的格子颜色是否相同,考虑是否对i+1行,j+1列操作;因此可以选择异或表示颜色操作次数对颜色的变化;0^1=1;0^0=0,1^1=0;
分两种情况:向下走:设当前格子(i,j)颜色为co1,(i+1,j)的颜色为co2;co1=a(i,j)^u^v ,co2 = a (i +1 , j) ^ v; (因为对(i,j)格子进行第 i 行操作时,不会改变i+1行的颜色);
向右走:设当前格子(i,j)颜色为co1,(i,j+1)的颜色为co2;co1=a(i,j)^u^v ;co2=a(i,j)^u;
因为对co1进行列操作时,a(i,j+1)列颜色不变;
综上:我们根据co1与co2是否相等进行转移;相等则不操作;否则计算代价;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2100;
const ll inf=1e18;
ll f[N][N][2][2]; //表示走到(i,j)这个格子,对r操作了0/1次,对c操作了0/1次
int a[N][N];
int r[N],c[N]; //行和列的花费
int main()
{
int h,w;
cin>>h>>w;
for(int i=1;i<=h;i++)
scanf("%d",&r[i]);
for(int i=1;i<=w;i++)
scanf("%d",&c[i]);
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
scanf("%1d",&a[i][j]);
//初始化
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
for(int u=0;u<2;u++)
for(int v=0;v<2;v++)
{
f[i][j][u][v]=inf;
}
f[1][1][0][0]=0;
f[1][1][1][0]=r[1];
f[1][1][0][1]=c[1];
f[1][1][1][1]=r[1]+c[1];
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
for(int u=0;u<2;u++)
for(int v=0;v<2;v++)
{
if(i+1<=h)
{
int co1=a[i][j]^u^v;
int co2=a[i+1][j]^v;
if(co1==co2)
f[i+1][j][0][v]=min(f[i+1][j][0][v],f[i][j][u][v]);//该行不选
else
f[i+1][j][1][v]=min(f[i+1][j][1][v],f[i][j][u][v]+r[i+1]);
}
if(j+1<=w)
{
int co1=a[i][j]^u^v;
int co2=a[i][j+1]^u;
if(co1==co2)
f[i][j+1][u][0]=min(f[i][j+1][u][0],f[i][j][u][v]);//相等不选该列
else
f[i][j+1][u][1]=min(f[i][j+1][u][1],f[i][j][u][v]+c[j+1]);
}
}
ll ans=inf;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
ans=min(ans,f[h][w][i][j]);
cout<<ans<<endl;
}