OIFC NOI2023省队集训
T1 绕口令 twister
字符串
题意
给定环形字符串 \(s\),对 \(k\) 从 \(1\) 到 \(n\),判断是否能删去一个长 \(k\) 子段,使得剩余部分无相邻相同字符。
考虑已有的相邻相同字符,必须截断。再删去一个子段可能导致剩余部分的两端变为相同,则问题可转为子段中是否存在距离为 \(k\) 的字符不同的位置。
做法1
考虑暴力做法,枚举 \(i\) 从 \(l\) 到 \(r\),再枚举 \(j\) 从 \(i+1\) 到 \(r\),如果 \(s_{j-1}=s_j\) 直接 break。考虑优化,发现只有 \(s_j=s_l\) 的 \(j-l\) 需要继续考虑 \((l+1,j+1)\),如果仍相等,会继续判断 \((l+2,j+2)\),一直进行下去知道不相等或碰到边界。发现碰到边界当且仅当 \(lcp(s_l,s_j)=r-j+1\) 用扩展 KMP 即可判断。
做法2
考虑不存在距离为 \(k\) 的字符不同的位置相当于 \(s[l:r-k]=s[l+k:r]\),用字符串哈希/KMP即可判断。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=2000005;
int n,ans[N],vis[N],len[N];
string s;
void calc(int l,int r){
int box=l;
len[l]=0;
for(int i=0;i<(r-l+1);i++) vis[i]=0;
vis[0]=1;
for(int i=l+1;i<=r;i++){
if(i<box+len[box]) len[i]=min(len[i-box+l],box+len[box]-i);
else len[i]=0;
while(i+len[i]<=r&&s[l+len[i]]==s[i+len[i]]) len[i]++;
if(i+len[i]>box+len[box]) box=i;
if(i+len[i]<=r) vis[i-l]=1;
}
}
bool solve(int Case){
if(!(cin>>s)) return false;
cout<<"Case "<<Case<<": ";
n=s.size();
s=" "+s+s;
for(int i=0;i<n;i++) ans[i]=0;
ans[n-1]=1;
vector<int> vec;
for(int i=1;i<=n;i++) if(s[i]==s[i+1]) vec.push_back(i);
if(vec.empty()){
calc(1,n);
ans[0]=1;
for(int i=0;i<n;i++){
if(i) ans[i-1]|=vis[i];
ans[n-i-1]|=vis[i];
}
for(int i=0;i<n;i++) cout<<ans[i];
cout<<'\n';
return true;
}
for(int ind=0;ind<vec.size();ind++){
int i=vec[ind],j=vec[(ind+1)%vec.size()];
if(j<=i) j+=n;
calc(i+1,j);
for(int k=0;k<j-i;k++) ans[n-k-1]|=vis[k];
}
for(int i=0;i<n;i++) cout<<ans[i];
cout<<'\n';
return true;
}
int main(){
#ifndef JZQ
freopen("twister.in","r",stdin);
freopen("twister.out","w",stdout);
#endif
ios::sync_with_stdio(false);
int T=0;
while(++T&&solve(T));
return 0;
}
T2 图论 graph
图论、网络流、匹配
题意
给定一个有向图,图上的一些边是“重要的”。你的任务是,求出至少需要多少条路径,才能覆盖所有“重要的”边至少一次。
路径可以不是简单路径,亦即可以经过同一个点或者同一条边多次。
做法1
缩强连通分量,把必经边拆成两条边+一个必经点。暴力的做法是,求传递闭包,取出必经点的生成子图求最小连覆盖。考虑最小连覆盖等于点数-拆点二分图最大匹配,传递闭包可以直接放在右部点内部。
具体地,对缩点拆边后的图 \(G=(V,E)\),对 \((u,v)\in E\),在拆点二分图上加入容量为 \(1\) 的 \((u,v')\) 和容量为 \(+\infty\) 的 \((u',v')\);对必经点 \(u\),在拆点二分图上加入容量为 \(+\infty\) 的 \((S,u)\) 和 \((u',T)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=50005,M=100005;
int n,m,dfn[N],low[N],tot,instack[N],id[N],vis[N+M];
stack<int> st;
vector<int> G[N];
void dfs(int u){
dfn[u]=low[u]=++tot,instack[u]=1;
st.push(u);
for(auto v:G[u]){
if(!dfn[v]){
dfs(v);
chkmin(low[u],low[v]);
}
else if(instack[v]) chkmin(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
while(st.top()!=u){
int v=st.top();
st.pop();
id[v]=u,instack[v]=0;
}
st.pop();
id[u]=u,instack[u]=0;
}
}
namespace Flow{
struct Edge{
int u,v,c,f;
Edge(){}
Edge(int u,int v,int c,int f):u(u),v(v),c(c),f(f){}
};
Edge es[(M*12)+(N<<2)];
int s,t,tot,dis[(N+M)<<1],vis[(N+M)<<1],cur[(N+M)<<1];
vector<int> G[(N+M)<<1];
void addEdge(int u,int v,int c){
G[u].push_back(tot);
es[tot++]=Edge(u,v,c,0);
G[v].push_back(tot);
es[tot++]=Edge(v,u,0,0);
}
bool bfs(){
for(int i=s;i<=t;i++) dis[i]=inf,vis[i]=cur[i]=0;
queue<int> q;
q.push(s),dis[s]=0;
while(q.size()){
int u=q.front();
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto eid:G[u]){
auto e=es[eid];
if(e.f<e.c&&dis[e.v]>dis[u]+1){
dis[e.v]=dis[u]+1;
q.push(e.v);
}
}
}
return dis[t]!=inf;
}
int dfs(int u,int t,int flow){
if(u==t) return flow;
int sum=0;
for(int &i=cur[u];i<G[u].size();i++){
auto &e=es[G[u][i]];
if(e.f<e.c&&dis[e.v]==dis[u]+1){
int tmp=dfs(e.v,t,min(flow-sum,e.c-e.f));
e.f+=tmp,es[G[u][i]^1].f-=tmp;
sum+=tmp;
}
if(sum==flow) break;
}
return sum;
}
int calc(){
int sum=0;
while(bfs()){
sum+=dfs(s,t,inf);
}
return sum;
}
}
void solve(){
scanf("%d%d",&n,&m);
vector<tuple<int,int,int>> es;
for(int i=1;i<=m;i++){
int u,v,t;
scanf("%d%d%d",&u,&v,&t);
es.push_back({u,v,t});
G[u].push_back(v);
}
for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i);
vector<pii> es2;
for(auto [u,v,t]:es){
if(t&&id[u]==id[v]) vis[id[u]]=1;
else if(t&&id[u]!=id[v]) vis[++n]=1,es2.push_back({id[u],n}),es2.push_back({n,id[v]});
else if(id[u]!=id[v]) es2.push_back({id[u],id[v]});
}
Flow::s=0,Flow::t=(n<<1)+1;
int cnt=0;
for(int i=1;i<=n;i++) if(vis[i]){
Flow::addEdge(0,i,1),Flow::addEdge(n+i,(n<<1)+1,1);
cnt++;
}
for(auto [u,v]:es2) Flow::addEdge(u,n+v,1),Flow::addEdge(n+u,n+v,inf);
printf("%d\n",cnt-Flow::calc());
}
int main(){
#ifndef JZQ
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
#endif
int T=1;
while(T--) solve();
return 0;
}
做法2
直接跑有源汇上下界最小流,把必经边流量下界设为 \(1\),源点连向每一个点,每一个点连向汇点。
先求有源汇上下界可行流,注意要连一条源点到汇点容量为 \(+\infty\) 的边 \(e\),然后加超级源点、超级汇点。
跑出可行流后,删去附加边,从 \(T\) 向 \(S\) 退流。
答案为 \(e\) 的流量 - 退流流量。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=50005,M=100005;
int n,m,s[N];
stack<int> st;
vector<int> G[N];
namespace Flow{
struct Edge{
int u,v,c,f;
Edge(){}
Edge(int u,int v,int c,int f):u(u),v(v),c(c),f(f){}
};
Edge es[(M<<1)+(N*6)];
int tot,dis[N],vis[N],cur[N];
vector<int> G[N];
void addEdge(int u,int v,int c){
G[u].push_back(tot);
es[tot++]=Edge(u,v,c,0);
G[v].push_back(tot);
es[tot++]=Edge(v,u,0,0);
}
bool bfs(int s,int t){
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=cur[i]=0;
queue<int> q;
q.push(s),dis[s]=0;
while(q.size()){
int u=q.front();
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto eid:G[u]){
auto e=es[eid];
if(e.f<e.c&&dis[e.v]>dis[u]+1){
dis[e.v]=dis[u]+1;
q.push(e.v);
}
}
}
return dis[t]!=inf;
}
int dfs(int u,int t,int flow){
if(u==t) return flow;
int sum=0;
for(int &i=cur[u];i<G[u].size();i++){
auto &e=es[G[u][i]];
if(e.f<e.c&&dis[e.v]==dis[u]+1){
int tmp=dfs(e.v,t,min(flow-sum,e.c-e.f));
e.f+=tmp,es[G[u][i]^1].f-=tmp;
sum+=tmp;
}
if(sum==flow) break;
}
return sum;
}
int calc(int s,int t){
int sum=0;
while(bfs(s,t)){
sum+=dfs(s,t,inf);
}
return sum;
}
}
void solve(){
scanf("%d%d",&n,&m);
vector<tuple<int,int,int>> es;
for(int i=1;i<=m;i++){
int u,v,t;
scanf("%d%d%d",&u,&v,&t);
Flow::addEdge(u,v,inf);
if(t) s[u]--,s[v]++;
}
int tmp=n;
int s0=++n,t0=++n,s1=++n,t1=++n;
for(int i=1;i<=tmp;i++) Flow::addEdge(s0,i,inf),Flow::addEdge(i,t0,inf);
for(int i=1;i<=tmp;i++){
if(s[i]>0) Flow::addEdge(s1,i,s[i]);
else if(s[i]<0) Flow::addEdge(i,t1,-s[i]);
}
Flow::addEdge(t0,s0,inf);
Flow::calc(s1,t1);
int ans=Flow::es[Flow::tot-2].f;
Flow::G[s0].pop_back(),Flow::G[t0].pop_back();
printf("%d\n",ans-Flow::calc(t0,s0));
}
int main(){
#ifndef JZQ
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
#endif
int T=1;
while(T--) solve();
return 0;
}
T3 配音演员 vocal
构造、图论
题意

做法
有没有可能不会无解?考虑证明,不难想到使用归纳法。
假设 \((n-1)\)-Benes 网络能够接受任意变换,只需决定每一个输入被放到左边还是右边。关注第一个交换器,发现0和1不能被放到相同一侧,进一步,发现第一行每一个交换器的输出都在不同两侧,最后一行每个交换器输入都在不同两侧。设 \(a_i\) 表示 \(i\) 被放到左/右侧,则 \(a_0\neq a_1,a_2\neq a_3,\dots\),\(a_{p_0}\neq a_{p_1},a_{p_2}\neq a_{p_3},\dots\)。这是一个黑白染色问题,直接跑即可。
考虑证明永远存在一个解:每一个点度数恒等于 \(2\),且边可以被黑白染色(第一行连的边不相邻、最后一行连的边不相邻),因此恒存在黑白染色方案。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=20,M=(1<<13)+5;
int n,p[M],q[M],tmp[M],ans[N<<1][M],vis[M],color[M];
vector<int> G[M];
void shuf(int p[],int l,int r){
int n=r-l;
for(int i=0;i<n;i++){
int j=(i>>1);
if(i&1) j|=(n>>1);
tmp[j+l]=p[i+l];
}
for(int i=l;i<r;i++) p[i]=tmp[i];
}
bool dfs(int u,int t){
if(vis[u]){
if(t==color[u]) return true;
return false;
}
vis[u]=1,color[u]=t;
for(auto v:G[u]) if(!dfs(v,t^1)) return false;
return true;
}
bool check(int l,int r){
for(int i=l;i<r;i++) vis[p[i]]=color[p[i]]=0;
for(int i=l;i<r;i++){
if(vis[p[i]]) continue;
if(!dfs(p[i],0)) return false;
}
return true;
}
bool solve(int l,int r,int row1,int row2){
if(r-l==2){
if(p[l]==q[l]) ans[row1][l>>1]=0;
else ans[row1][l>>1]=1;
return true;
}
for(int i=l;i<r;i++) G[p[i]].clear();
for(int i=l;i<r;i+=2){
G[p[i]].push_back(p[i+1]),G[p[i+1]].push_back(p[i]);
G[q[i]].push_back(q[i+1]),G[q[i+1]].push_back(q[i]);
}
if(!check(l,r)) return false;
for(int i=l;i<r;i+=2){
ans[row1][i>>1]=color[p[i]];
if(color[p[i]]) swap(p[i],p[i+1]);
}
for(int i=l;i<r;i+=2){
ans[row2][i>>1]=color[q[i]];
if(color[q[i]]) swap(q[i],q[i+1]);
}
int mid=(l+r)>>1;
shuf(p,l,r),shuf(q,l,r);
return solve(l,mid,row1+1,row2-1)&&solve(mid,r,row1+1,row2-1);
}
bool solve(bool flag){
scanf("%d",&n);
if(!n) return false;
if(flag) printf("\n");
for(int i=0;i<(1<<n);i++) scanf("%d",&q[i]);
for(int i=0;i<(1<<n);i++) p[i]=i;
if(!solve(0,1<<n,0,(n-1)<<1)){
printf("impossible\n");
return false;
}
for(int i=0;i<=((n-1)<<1);i++){
for(int j=0;j<(1<<(n-1));j++) printf("%d",ans[i][j]);
printf("\n");
}
return true;
}
int main(){
#ifndef JZQ
freopen("vocal.in","r",stdin);
freopen("vocal.out","w",stdout);
#endif
bool flag=false;
while(solve(flag)) flag=true;
return 0;
}

浙公网安备 33010602011771号