ECfinal2021部分题解
把赛中卡住的题争取补一下
题目链接:https://codeforces.com/gym/103861
H. Check Pattern is Good
网络流
先把各自按奇偶反色,然后就变成要最多的全黑或全白
建立一个两侧各是n*n的二分图
左边表示将这个小矩形,右边表示将这个小矩形染成黑色
相交的黑白小矩形之间连接inf
如果还有希望变白的小矩形,就让s连到它,流量为1
如果还有希望变白的小矩形,就让它连到t,流量为1
跑最小割就可以了
不难感受到这种建图的正确性
这题代码就先咕咕咕了
G.Check Pattern is Bad
做法其实很简单
先把一定要填的?,即填某种字符会出现不合法的?,先填了
如果不存在,就随便指定一个?为随便一种字符,然后重复上述过程即可
最后跑出来有解就是有解,没解就是没解
目前没有严格证明
当时再考场上可以大概感受到随便填一下基本上也是有解的
当时因为想了一个假填法,所以已经写完了bfs的过程,但后面卡在了填未知?的位置
赛中觉得是随机个几次,应该就能过
事实证明随机一次就是一定有解的。
但当时手里卡着别的题,榜上过的人也不多,我也没有严格证明就没敢冲了。还是有点可惜
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PI;
const int N=105;
char ss[N][N];
int n,m;
queue<PI> q;
int tx[4]={0,0,1,1};
int ty[4]={0,1,0,1};
bool check1 (int x,int y)
{
if (x<=0||x>=n||y<=0||y>=m) return false;
if (ss[x][y]=='B'&&ss[x+1][y]=='W'&&ss[x][y+1]=='W'&&ss[x+1][y+1]=='B') return false;
if (ss[x][y]=='W'&&ss[x+1][y]=='B'&&ss[x][y+1]=='B'&&ss[x+1][y+1]=='W') return false;
return true;
}
void check (int x,int y)
{
if (x<=0||x>=n||y<=0||y>=m) return ;
int xx=-1,yy=-1;
for (int u=0;u<4;u++)
{
int nx=x+tx[u],ny=y+ty[u];
if (ss[nx][ny]=='?')
{
if (xx!=-1) return ;
xx=nx;yy=ny;
}
}
if (xx==-1) return ;
ss[xx][yy]='B';
if (check1(x,y)==false) {ss[xx][yy]='W';q.push({xx,yy});return ;}
ss[xx][yy]='W';
if (check1(x,y)==false) {ss[xx][yy]='B';q.push({xx,yy});return ;}
ss[xx][yy]='?';
}
void solve ()
{
while (!q.empty())
{
int x=q.front().first,y=q.front().second;q.pop();
for (int u=-1;u<=1;u++)
for (int i=-1;i<=1;i++)
check(x+u,y+i);
}
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
for (int u=1;u<=n;u++) scanf("%s",ss[u]+1);
for (int u=1;u<n;u++)
for (int i=1;i<m;i++)
check(u,i);
solve();
for (int u=1;u<=n;u++)
for (int i=1;i<=m;i++)
if (ss[u][i]=='?')
{
ss[u][i]='B';
q.push({u,i});
solve();
}
bool tf=true;
for (int u=1;u<n;u++)
for (int i=1;i<m;i++)
if (check1(u,i)==false)
tf=false;
if (!tf) {printf("NO\n");continue;}
printf("YES\n");
for (int u=1;u<=n;u++)
{
for (int i=1;i<=m;i++) printf("%c",ss[u][i]);
printf("\n");
}
}
return 0;
}
C.String-dle Count:
其实,最后每一种字符只有两种状态:
1.出现了x,此时就已经知道该字符有多少个了
2.没有出现x,那么相当于知道了这个字符至少有多少个记为\(L_I\)
同时,我们可以维护出每一个位置不可以填某个字符
考虑从左往右填,记录一个状态为填了前i个字符
由于有下界的限制,因此还需要每种字符已经出现了多少次
由于\(\sum L_i\le k\),因此可以简单地压成一个二进制数
至于具体出现了多少次的字符,只需要保证再出现够\(L_i\)个字符后不再转移即可
目前的写法是O(\(262^kk\))的,但其实可以写成\(O(2^kk)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD=1e9+7;
const int N=10005;
const int K=20;
int add (int x,int y){x=x+y;return x>=MOD?x-MOD:x;}
int mul (int x,int y){return (LL)x*y%MOD;}
int dec (int x,int y){x=x-y;return x<0?x+MOD:x;}
int Pow (int x,int y)
{
int t=1;
while (y)
{
if (y&1) t=mul(t,x);
y>>=1;x=mul(x,x);
}
return t;
}
int L[30],R[30];
char s[K],t[K];
int n,m;
int cnt[30];
bool ban[K][30];
bool p[N];
void Init()
{
scanf("%s%s",s,t);
for (int u=0;u<26;u++) cnt[u]=0;
for (int u=0;u<m;u++) if (t[u]=='O')
{
int x=s[u]-'A';
cnt[x]++;
for (int i=0;i<26;i++) if (i!=x) ban[u][i]=1;
}
for (int u=0;u<m;u++) if (t[u]!='O')
{
int x=s[u]-'A';
ban[u][x]=1;
if (t[u]=='-') cnt[x]++;
else {p[x]=false;R[x]=min(R[x],cnt[x]);}
}
for (int u=0;u<26;u++) L[u]=max(L[u],cnt[u]);
}
int mask[30];
int bel[30];
int f[K][1<<K];
int inv[30];
int main()
{
inv[0]=1;for (int u=1;u<=20;u++) inv[u]=mul(inv[u-1],Pow(u,MOD-2));
memset(ban,false,sizeof(ban));
for (int u=0;u<26;u++) p[u]=true;
scanf("%d%d",&n,&m);
for (int u=0;u<26;u++) {L[u]=0;R[u]=m;}
while (n--) Init();
for (int u=0;u<26;u++) if (L[u]>R[u]) {printf("0\n");return 0;}
n=0;
for (int u=0;u<26;u++)
{
mask[u]=0;
for (int i=0;i<L[u];i++)
{
mask[u]|=(1<<n);bel[n]=u;n++;
if (n>m) {printf("0\n");return 0;}
}
}
f[0][0]=1;
for (int u=0;u<m;u++)
for (int i=0;i<(1<<n);i++)
{
if (!f[u][i]) continue;
for (int k=0;k<n;k++) if (!ban[u][bel[k]]&&!((i>>k)&1))
{
f[u+1][i|(1<<k)]=add(f[u+1][i|(1<<k)],f[u][i]);
}
for (int k=0;k<26;k++) if (p[k]&&(mask[k]==(mask[k]&i))&&!ban[u][k])
{
f[u+1][i]=add(f[u+1][i],f[u][i]);
}
}
int ans=f[m][(1<<n)-1];
for (int u=0;u<26;u++) ans=mul(ans,inv[L[u]]);
printf("%d\n",ans);
return 0;
}