海亮寄 7.11

前言

业精于勤荒于嬉,行成于思而毁于随

正文(加餐)

连通分量选讲,这是要 \(DAY^{-1}\) 的节奏

浅贴一个课件

关键词:多源最短路、前置转化、临界分割

再浅贴一下题单

机房里有两个人疑似获得了情侣 buff,手速和代码准确率获得 \(2\) 倍加成

T1

先丢进来一个板子

题意

给定一个无向图,问有多少个点满足:删除该点后图变成一棵树

题解

度数 \(m-n+2\) 是容易判断的!

连通性也需要保证,所以要求度数合法的点不可以是割点

corner case 的特判是:一棵树 + 孤立点(然而其实不判也能过?)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5;
int n,m,head[N],tot=1,rt;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,deg[N];bool cut[N];
set<int> S;
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;
    int ch=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]&&u!=rt)cut[u]=true;
            if(u==rt)ch++;
        }else low[u]=min(low[u],dfn[v]);
    }
    if(ch>=2&&u==rt)cut[u]=true;
    return;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        add(u,v),add(v,u);
        deg[u]++,deg[v]++;
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])rt=i,tarjan(i);
    for(int i=1;i<=n;i++)
        if(!cut[i]&&deg[i]==m-n+2)S.insert(i);
    cout<<S.size()<<'\n';
    for(auto x:S)cout<<x<<' ';
    return 0;
}

T2

一题三解,收获颇丰

题意

给定有向图,点有点权 \(c_i\),求最大的 \(c_y - c_x\) 使得存在一条 \(1 \to x \to y \to n\) 的路径

题解

法一:缩点起手,DAG 上 DP,随便维护

法二:分层图,层层之间的边权表示买入或卖出的活动,直接 spfa 最长路或者利用小性质转化一下变为 dijkstra 最短路

法三:判可达性后直接按 \(c_i\) 升序排序,均摊复杂度 \(O(n+m)\),可以通过

代码

给一份缩点实现的,毕竟专题是连通分量专题

点击查看代码
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e5+5,M=5e5+5;
int n,m,c[N],head[N],tot=1;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,col[N],cnt;
int stk[N],tp,mx[N],mn[N];bool instk[N];
map<pii,bool> mrk;vi G[N];int f[N],ans[N];
int in[N],dp[N];
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;stk[++tp]=u;instk[u]=true;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);}
        else if(instk[v])low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        cnt++;
        int now=-1;
        while(now!=u){
            now=stk[tp--];instk[now]=false;
            col[now]=cnt;
            mx[cnt]=max(mx[cnt],c[now]);
            mn[cnt]=min(mn[cnt],c[now]);
        }
        f[cnt]=mn[cnt];
    }
    return;
}
inline void topo(){
    for(int u=1;u<=cnt;u++)for(int v:G[u])in[v]++;
    int S=col[1];
    queue<int> q;q.push(S);
    f[S]=mn[S];ans[S]=mx[S]-mn[S];
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int v:G[u]){
            f[v]=min(f[v],f[u]);ans[v]=max(ans[v],ans[u]);
            in[v]--;
            if(!in[v]){ans[v]=max(ans[v],mx[v]-f[v]);q.push(v);}
        }
    }

}
int main(){
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>c[i];
    for(int i=1;i<=m;i++){
        int u,v,opt;cin>>u>>v>>opt;
        if(opt==1)add(u,v);
        else if(opt==2)add(u,v),add(v,u);
    }
    memset(mn,0x3f,sizeof(mn));
    tarjan(1);
    // for(int i=1;i<=cnt;i++)cerr<<i<<' '<<mx[i]<<' '<<mn[i]<<' '<<f[i]<<endl;
    // cerr<<'\n';
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(col[u]==col[v]||!col[u]||!col[v])continue;
            if(mrk[mkp(col[u],col[v])])continue;
            G[col[u]].pb(col[v]);mrk[mkp(col[u],col[v])]=true;
        }
    }
    topo();
    cout<<ans[col[n]]<<'\n';
    return 0;
}

T3

当我开 T3 的时候,隔壁墨软二人组都已经干到 T7 力!

题意

给定有向图,判断该图是否满足以下条件:

  • 对任意一对点 \(x,y\),均满足:存在一条 \(x \to y\) 的路径或存在一条 \(y \to x\) 的路径

题解

缩点起手,题意的一个充要转化是对于缩点后的 DAG 拓扑序的队列内的元素个数总是不超过 \(1\)

课上还有人提出了另一种转化,即判断最长链是否涵盖所有结点

(目前没有人给出反例,但也没有证明该结论的正确性)

代码

点击查看代码
#include<bits/stdc++.h>
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e3+5,M=6e3+5;
int n,m,head[N],tot=1;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,col[N],cnt;
int stk[N],tp;bool instk[N];
vi G[N];int in[N];
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;stk[++tp]=u;instk[u]=true;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);}
        else if(instk[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        cnt++;
        int now=-1;
        while(now!=u){
            now=stk[tp--];
            instk[now]=false;
            col[now]=cnt;
        }
    }
    return;
}
inline bool topo(){
    queue<int> q;
    for(int i=1;i<=cnt;i++)
        if(!in[i])q.push(i);
    while(!q.empty()){
        if(q.size()>1)return false;
        int u=q.front();q.pop();
        for(int v:G[u]){
            in[v]--;
            if(!in[v])q.push(v);
        }
    }
    return true;
}
inline void solve(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        add(u,v);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);
    for(int i=1;i<=cnt;i++)G[i].clear();
    memset(in,0,sizeof(in));
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(col[u]==col[v])continue;
            G[col[u]].pb(col[v]);in[col[v]]++;
        }
    }
    if(topo())cout<<"I love you my love and our love save us!\n";
    else cout<<"Light my fire!\n";
    return;
}
inline void init(){
    memset(head,0,sizeof(head));tot=1;
    memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));tim=0;
    memset(col,0,sizeof(col));cnt=0;
    memset(instk,0,sizeof(instk));memset(stk,0,sizeof(stk));tp=0;
    return;
}
int main(){
    // freopen("in.txt","r",stdin);
    // freopen("ans.txt","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;cin>>T;while(T--)init(),solve();
    return 0;
}

T4

20 min 切,开心!

题意

给定一个连通无向图,要求选取 \(S,T\) 使得从 \(S\)\(T\) 的必经边尽可能多

题解

边双缩成一棵树后,跑一个树的直径

代码

最水的一集

点击查看代码
#include<bits/stdc++.h>
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=3e5+5,M=3e5+5;
int n,m,head[N],tot=1;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,col[N],cnt;
int stk[N],tp;vi G[N];int dp[N],ans;
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u,int pre){
    dfn[u]=low[u]=++tim;stk[++tp]=u;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if((i^1)==pre)continue;
        if(!dfn[v]){tarjan(v,i);low[u]=min(low[u],low[v]);}
        else low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        cnt++;
        int now=-1;
        while(now!=u){
            now=stk[tp--];
            col[now]=cnt;
        }
    }
    return;
}
inline void dfs(int u,int fa){
    for(int v:G[u]){
        if(v==fa)continue;
        dfs(v,u);
        ans=max(ans,dp[u]+dp[v]+1);
        dp[u]=max(dp[u],dp[v]+1);
    }
    return;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i,-1);
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(col[u]==col[v])continue;
            G[col[u]].pb(col[v]);
        }
    }
    dfs(1,-1);
    cout<<ans<<'\n';
    return 0;
}

T5

疑似简单题

题意

给定一个无向连通图,要求对所有的点求出:如果删除这个点,将会有多少对点不互相连通

题解

点双是显然的

答案与割点在 DFS 生成树上裂开的子树 \(siz\) 有关,具体是一个 \(\prod siz_v \times (n-siz_v-1)\) 的形式

Warning:

  • 割点的父辈们不要忘记加入贡献

  • 割点本身不是被删除,而是被“封锁”,所以也有贡献!

代码

就是割点板子上多加了几句贡献计算

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,M=5e5+5;
int n,m,head[N],tot=1,rt;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,siz[N];int ans[N];
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;siz[u]=1;
    int sum=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v);siz[u]+=siz[v];
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]){ans[u]+=(siz[v]*sum);sum+=siz[v];}
        }else low[u]=min(low[u],dfn[v]);
    }
    ans[u]+=(n-sum-1)*sum;ans[u]+=n-1;
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);
    for(int i=1;i<=n;i++)cout<<ans[i]*2<<'\n';
    return 0;
}

T6

第一眼,好难,不会做;第二眼,还是不会做;第三眼,嘶——金牌点双第一题;第四眼,rz 题

题意

给定一个无向图,问至少要设置多少个关键点使得,删除任意一个点后,所有点都能走到其中一个关键点,并求方案数

题解

求点双很显然,发现对于每一个点双:

  1. 如果其包含 \(>1\) 个割点,那么对答案无贡献

  2. 恰好包含一个割点,意味着点双内需要一个关键点

  3. 不包含割点,则需要两个关键点

关键点数目出来了,方案数自然好算

代码

不开 long long 见祖宗!

还有喜闻乐见的多测清空环节!

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e3+5,M=505;
int n,m,head[N],tot=1,rt;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,cnt;
int stk[N],tp;vi dcc[N];bool cut[N];
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;stk[++tp]=u;
    if(u==1&&head[u]==0){dcc[++cnt].pb(u);return;}
    int fa=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]){
                fa++;
                if(u!=1||fa>1)cut[u]=true;
                cnt++;
                int now=-1;
                while(now!=v){
                    now=stk[tp--];
                    dcc[cnt].pb(now);
                }
                dcc[cnt].pb(u);
            }
        }else low[u]=min(low[u],dfn[v]);
    }
    return;
}
inline void clr(){
    memset(head,0,sizeof(head));tot=1;
    memset(dfn,0,sizeof(dfn));tim=0;
    memset(low,0,sizeof(low));
    memset(cut,0,sizeof(cut));
    memset(stk,0,sizeof(stk));tp=0;
    for(int i=1;i<=cnt;i++)dcc[i].clear();
    n=cnt=0;
    return;
}
signed main(){
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T=0;
    while(cin>>m){
        if(m==0)break;
        for(int i=1;i<=m;i++){
            int u,v;cin>>u>>v;
            add(u,v),add(v,u);
            n=max(n,max(u,v));
        }
        tarjan(1);
        int ans1=0,ans2=1;
        for(int i=1;i<=cnt;i++){
            int sum=dcc[i].size(),tol=0;
            for(auto x:dcc[i])tol+=cut[x];
            if(tol==1)ans1++,ans2*=(sum-1);
            else if(tol==0)ans1+=2,ans2*=(sum*(sum-1)/2);
        }
        cout<<"Case "<<(++T)<<": "<<ans1<<" "<<ans2<<"\n";
        clr();
    }
    return 0;
}

T7

铁打的 shr,流水的 luogu 题

题意

直接去翻原题吧,难以概括……

题解

强连通分量内贡献好算

DAG 上发现选取度数为 \(0\) 的点一定最优

得到了一个近似正解的做法

然后发现一些 corner case

对于一些大小为 \(1\) 且入度为 \(0\) 的强连通分量,如果其领域内不存在入度为 \(1\) 的点,则可以不用选中该强连通分量,即对答案贡献减一

代码

隔壁火腿肠开 long double 莫名其妙 94pts,云落瞪了一下没瞪出来

(但其实云落的代码实现用 long double 可过,比较神秘)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db long double
#define vi vector<int>
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
using namespace std;
const int N=1e5+5,M=3e5+5;
int n,m,head[N],tot;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,col[N],cnt,in[N];
int stk[N],tp;bool instk[N];vi scc[N];
map<pii,bool> mp;
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;stk[++tp]=u;instk[u]=true;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);}
        else if(instk[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        cnt++;
        int now=-1;
        while(now!=u){
            now=stk[tp--];instk[now]=false;
            col[now]=cnt;scc[cnt].pb(now);
        }
    }
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1,u,v;i<=m;i++)cin>>u>>v,add(u,v);
    for(int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(col[u]==col[v]||mp[mkp(col[u],col[v])])continue;
            in[col[v]]++;mp[mkp(col[u],col[v])]=true;
        }
    }
    int sum=0;
    for(int i=1;i<=cnt;i++)
        if(!in[i])sum++;
    for(int k=1;k<=cnt;k++){
        if((int)(scc[k].size())>1||in[k])continue;
        int u=scc[k][0];bool flag=false;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(in[col[v]]==1)flag=true;
        }
        if(!flag){sum--;break;}
    }
    db ans=1.0-1.0*sum/n;
    cout<<fixed<<setprecision(6)<<ans<<'\n';
    return 0;
}

T8

锦鲤抄……

题意

给你一张有向图,每个点有一个点权。任意时刻你可以任意选择一个有入度的点,获得
它的点权并把它和它的出边从图上删去。最多能选择 \(k\) 个点,求最多能获得多少点权

题解

缩点起手,发现一个 SCC 内可以选择至少 \(siz-1\) 个点。特别地,如果存在入度或者自环,则可以都选

对于 DAG 的部分,按反向拓扑序取,可以取走所有的点

把所有可以扔进去的点扔到一个 vector 里,排序取前 \(k\) 大即可

代码

(因为按编号排序只 WA 两个点,\(FeSO_4\) 的同时还硬控自己半个点)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=5e5+5,M=2e6+5;
int n,m,k,a[N],head[N],tot;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,col[N],cnt,in[N];
int stk[N],tp;bool instk[N];vi scc[N],vec;
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;stk[++tp]=u;instk[u]=true;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);}
        else if(instk[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        cnt++;
        int now=-1;
        while(now!=u){
            now=stk[tp--];instk[now]=false;
            col[now]=cnt;scc[cnt].pb(now);
        }
    }
    return;
}
inline bool cmp(int x,int y){return a[x]>a[y];}
inline bool cmp1(int x,int y){return x>y;}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1,u,v;i<=m;i++)cin>>u>>v,add(u,v);
    for(int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(u==v||col[u]!=col[v])in[col[v]]++;
        }
    }
    for(int i=1;i<=cnt;i++){
        if(scc[i].size()==1){
            if(in[i])vec.pb(a[scc[i][0]]);
            continue;
        }
        sort(scc[i].begin(),scc[i].end(),cmp);
        for(auto x:scc[i])vec.pb(a[x]);
        if(!in[i])vec.pop_back();
    }
    sort(vec.begin(),vec.end(),cmp1);
    int ans=0;
    for(int i=0;i<min(k,(int)(vec.size()));i++)ans+=vec[i];
    cout<<ans<<'\n';
    return 0;
}

T9

神秘结论题

题意

\(n\) 个人,给出 \(m\) 个关系表示两个人不互相憎恶,其中 \(k\) 个人能进行一次会议当且仅当:

  • \(k\)\(>1\) 的奇数

  • \(k\) 个人坐一圈能满足任意相邻两人不相互憎恶

问有多少个人不能参加任何一场会议

题解

一次会议等价于一个奇环,问题等价于求有多少个点不在任意一个奇环内

结论:若一个点双内包含奇环,则该点双内所有点总是存在于至少一个简单奇环上

结论证明的核心:奇环的另一种表述——对于奇环上的任意两个结点,总存在两条不同的路径,使得其经过的边数奇偶性不同!

有了上面的结论,就可以直接把点双拎出来

判断奇环经典二分图染色即可

代码

结论题还是太需要脑子了,好在不怎么费手

点击查看代码
#include<bits/stdc++.h>
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e5+5,M=1e6+5;
int n,m,head[N],tot=1,rt;
struct Edge{int to,nxt;}e[M<<1];
int dfn[N],low[N],tim,col[N],cnt;
int stk[N],tp;vi dcc[N];
bool flag,mrk[N];int idx[N],c[N],now;
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void tarjan(int u){
    dfn[u]=low[u]=++tim;
    if(u==rt&&head[u]==0)dcc[++cnt].pb(u);
    stk[++tp]=u;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]){
                cnt++;
                int now=-1;
                while(now!=v){
                    now=stk[tp--];
                    dcc[cnt].pb(now);
                }
                dcc[cnt].pb(u);
            }
        }else low[u]=min(low[u],dfn[v]);
    }
    return;
}
inline void dfs(int u,int col){
    if(flag)return;
    c[u]=col;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(idx[v]!=now)continue;
        if(c[v]==col){flag=true;return;}
        if(!c[v])dfs(v,3-col);
    }
    return;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        if(u==v)continue;
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])rt=i,tarjan(i);

    for(int i=1;i<=cnt;i++){
        now=i;flag=false;
        for(auto x:dcc[i])idx[x]=now,c[x]=0;
        dfs(dcc[i][0],1);
        if(!flag)continue;
        for(auto x:dcc[i])mrk[x]=true;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        if(!mrk[i])ans++;
    cout<<ans<<'\n';
    return 0;
}

T10

居然自己灵光一闪想对了,开心!

题意

给定一个无向图,其中每个点的度数 \(\le 3\)。若每条边的容量为 \(1\),设 \(f_{x,y}\) 表示 \(x \to y\) 的最大流(或最小割),求 \(\sum \limits_{x<y} f(x,y)\)

题解

观测到 \(f(x,y) \le 3\),考虑对 \(f(x,y) = 0/1/2/3\) 计数

\(f(x,y)=0\) 等价于判连通

\(f(x,y)=1\) 等价于边双缩点

\(f(x,y)=2/3\) 没有直接思路,观测到 \(n,m\) 并非 \(10^5\),可以搞一个 \(O(nm)\) 量级的算法

枚举割边,对 \(f(x,y)=2\) 转化为 \(f(x,y)=1\),跑边双

对于每一个结点 \(u\),维护 \(O(m)\) 次边双编号

容易发现,\(f(x,y)=3\) 当且仅当,\(x,y\) 的由边双编号构成的序列是相同的,直接序列哈希维护

代码

(时间紧迫,没有代码)

T11

这能是黑题?思维难度顶天上位蓝

题意

给定一个推箱子游戏的局面,和 \(Q\) 次询问:是否能将箱子推到指定位置

题解

条件反射地,搞一个状态 \(f_{x,y,d}=0/1\) 表示箱子在 \((x,y)\),人在 \(d\) 方向的情况 是 / 否 存在

转移是需要判断箱子不动的时候,人是否可以移动到相应方向的

容易发现这个东西很点双,预处理即可

代码

(时间紧迫……)

T12

不会咋办?咕咕咕——

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-11 21:01  sunxuhetai  阅读(7)  评论(0)    收藏  举报