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;
}
posted @ 2025-10-21 09:43  SmpaelFx  阅读(24)  评论(0)    收藏  举报