AtCoder Beginner Contest 259 题解
比较懒,所以打算写一些 ABC 的题解混混时间。
同样也是因为比较懒,所以就只写 G 和 Ex 的题解了。
G - Grid Card Game
一眼最小割,不知道为啥就是像最小割。所以前置知识是显然的了。
重新描述一下问题就是:
- 可以选择一些行列,给答案加上行列的权值和。
- 一个负权格子不能同时选上它在的行列。
- 一个非负权格子行列同时被选上的时候要减掉自己的权值(因为它贡献给答案贡献了两次)。
首先考虑先选择所有对答案贡献非负的行和列,然后就只要考虑如何去掉重复贡献的非负权格子和强制不同时选择负权格子所在的行列。
对于一个格子来说,要么是行或者列有至少一个不选,要么本身是非负权格子并且去掉自己重复的一次贡献。
这里应该就有点像最小割了。
建图,每一非负整行、每一非负整列都存在对应结点。从源点向每个贡献非负的行点连最大流量为行权值和的边,列同理,但联向汇点。
每个格子就在所在行列之间连边,负点就连无穷大的权值,代表不能割这条边,非负点就连自己格子的权值,割掉就是删去自己重复的贡献。
这样每个格子都可以通过割掉行列对应的点连到源点汇点的边来成为只有行或者列选择了它的格子。也可以割掉自己的边来去掉重复贡献。
然后答案就是每一非负整行、每一非负整列的权值和减去最小割。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct edge{
int to,rev;
ll cap;
edge(){
to=cap=rev=0;
}
edge(int _t,ll _c,int _r){
to=_t;
cap=_c;
rev=_r;
};
};
vector<edge> g[205];
int iter[205],dis[205];
void addedge(int from,int to,ll cap){
g[from].emplace_back(to,cap,g[to].size());
g[to].emplace_back(from,0,g[from].size()-1);
}
void getdis(int src){
memset(dis,-1,sizeof(dis));
queue<int> q;
q.emplace(src);
dis[src]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(edge &e:g[x])if(e.cap&&dis[e.to]==-1){
dis[e.to]=dis[x]+1;
q.emplace(e.to);
}
}
}
ll dfs(int x,int dst,ll flow){
if(x==dst)return flow;
for(int &i=iter[x];i<g[x].size();i++){
edge &e=g[x][i];
if(e.cap&&dis[x]<dis[e.to]&&flow){
ll cflow=dfs(e.to,dst,min(flow,e.cap));
e.cap-=cflow;
g[e.to][e.rev].cap+=cflow;
if(cflow)return cflow;
}
}
return 0;
}
ll dinic(int src,int dst){
ll flow=0;
while(true){
getdis(src);
if(dis[dst]==-1)break;
memset(iter,0,sizeof(iter));
ll cflow=0;
while(cflow=dfs(src,dst,1e18))flow+=cflow;
}
return flow;
}
const ll C=2e13;
int n,m,S,T,cnt;
ll a[105][105],sr[105],sc[105];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
T=n+m+1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
sr[i]+=a[i][j];
sc[j]+=a[i][j];
}
}
ll ans=0;
for(int i=1;i<=n;i++){
if(sr[i]>0){
addedge(S,i,sr[i]);
ans+=sr[i];
}
}
for(int i=1;i<=m;i++){
if(sc[i]>0){
addedge(n+i,T,sc[i]);
ans+=sc[i];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
addedge(i,n+j,a[i][j]<0?C:a[i][j]);
}
}
cout<<ans-dinic(S,T)<<'\n';
return 0;
}
H - Yet Another Path Counting
首先假设全图都是一种颜色怎么做,维护一个 \(dp_{i,j}\) 表示有多少种方式从一个格子出发到达第 \(i\) 行第 \(j\) 列的格子。
转移是容易的,直接从上方和左方走过来,然后加上以当前格子作为起点的方案。答案就是整个 \(dp\) 数组的和。
然后转换成你只考虑图上的一部分格子能作为起点和终点怎么做,也就是只考虑一种颜色(叫做颜色 \(C\))时候的做法。
转移还是直接从上方和左方走过来,然后如果当前格子的颜色是 \(C\) 就加上以当前格子作为起点的方案。答案就是所有颜色 \(C\) 的格子对应的 \(dp\) 值的和。
然后这是 \(O(N^4)\) 的,应该不能过吧。
考虑一种颜色 \(C\) 的格子比较少怎么做。
枚举该颜色的起点和终点,假设起点到终点走了 \(r\) 行 \(c\) 列。那么答案就是假设全图都一种颜色时的 \(dp_{r,c}\),可以预处理一下。
然后就是考虑比较少怎么定义。假设这个比较少的格子最多为 \(B\)。
当格子比较少的时候,一次计算 \(O(B^2)\),最多 \(O(\frac {N^2} B)\) 次运算,最坏 \(O(N^2B)\)。
当格子比较少的时候,一次计算 \(O(N^2)\),最多 \(O(\frac {N^2} B)\) 次运算,最坏 \(O(\frac {N^4} B)\)。
所以取 \(B=N\) 应该是不错的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
int n,a[405][405],f[405][405],dp[405][405];
vector<pair<int,int>> vc[400*400+5];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
vc[a[i][j]].emplace_back(i,j);
}
}
f[0][0]=1;
for(int i=1;i<n;i++){
f[0][i]=f[i][0]=1;
}
for(int i=1;i<n;i++)for(int j=1;j<n;j++)f[i][j]=(f[i-1][j]+f[i][j-1])%mod;
int ans=0;
for(int c=1;c<=n*n;c++)if(vc[c].size()<=n){
for(int i=0;i<vc[c].size();i++)for(int j=0;j<vc[c].size();j++){
int dx=vc[c][j].first-vc[c][i].first,dy=vc[c][j].second-vc[c][i].second;
if(dx>=0&&dy>=0)ans=(ans+f[dx][dy])%mod;
}
}else{
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=a[i][j]==c;
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
if(a[i][j]==c)ans=(ans+dp[i][j])%mod;
}
}
}
cout<<ans<<'\n';
return 0;
}

浙公网安备 33010602011771号