Educational Codeforces Round 56 (Rated for Div. 2) D. Beautiful Graph(二分图判定+计数)

博客围绕无向图填数问题展开,要求在图结点填入1、2、3,使边相连两点权值和为奇数,求填数方案数。解题思路是二分图判定,分奇偶层染色,统计奇数点和偶数点个数,以其求2次幂之和,枚举起点得总方案数,还提及无解情况及注意点。

题意
给一个图,图上的点可以被染成权值为1,2,3

令边权=两个点的点权和,求令边权为奇数的所有方案数%998244353

题解
首先二分图判定一下,分奇偶层;

奇层染奇数,偶层染偶数;

或奇层染偶数,偶层染奇数。

对于每个连通分量,其方案数为(modpow(2,奇层点数,MOD)+modpow(2,偶层点数,MOD))%MOD

这个modpow是快速幂。

然后整个图的就是所有连通分量的乘积%MOD了。

AC代码:

#include <iostream>
#include <algorithm> 
#include <cstring>
#include <cstdio>
#include <cmath>
#include <set>
#include <map>
#include <vector>
#include <stack>
#include <queue>
#include <functional>
const int INF=0x3f3f3f3f;
const int maxn=3e5+10; 
const int mod=1e9+7;
const int MOD=998244353;
const double eps=1e-7;
typedef long long ll;
#define vi vector<int> 
#define si set<int>
#define pii pair<int,int> 
#define pi acos(-1.0)
#define pb push_back
#define mp make_pair
#define lowbit(x) (x&(-x))
#define sci(x) scanf("%d",&(x))
#define scll(x) scanf("%I64d",&(x))
#define sclf(x) scanf("%lf",&(x))
#define pri(x) printf("%d",(x))
#define rep(i,j,k) for(int i=j;i<=k;++i)
#define per(i,j,k) for(int i=j;i>=k;--i)
#define mem(a,b) memset(a,b,sizeof(a)) 
using namespace std;
int cnt,head[maxn],color[maxn],shu;
int t,n,m,pos[maxn],neg[maxn],now;


struct edge
{
    int to , nxt;
}e[maxn << 1];

void init(int n)
{
    for(int i=0;i<n;++i)
        color[i]=0,head[i]=-1;
    cnt=0;
    shu=1;
}

void add(int u , int v){
    e[cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt++;  
}

ll modpow(ll x,ll n,ll mod)
{
    if(n==0)return 1;
    ll res=modpow(x,n/2,mod),ans=res*res;
    if(ans>=mod)ans%=mod;
    if(n&1)ans=ans*x;
    if(ans>=mod)ans%=mod;
    return ans;
}

bool judge(int u , int c){
    color[u] = c;
    if(c > 0) pos[c]++;
    else {
        int p = -c;
        neg[p]++;
    }
    for(int i = head[u]; ~i ; i = e[i].nxt){
        int v = e[i].to;
        if(color[v] == c) return 0;
        if(color[v] == 0 && !judge(v,-c))
            return 0;
    }
    return 1;
}

ll solve(){
    ll ans = 1;
    for(int i = 0 ;i < n; i++){
        if(color[i] == 0){
            pos[shu] = 0;
            neg[shu] = 0;
            if(!judge(i,shu)) return 0;
            shu++;
        }
    }
    rep(i,1,shu-1){
        ll tmp1 = modpow(2,pos[i],MOD);
        ll tmp2 = modpow(2,neg[i],MOD);
        ll q = tmp1 + tmp2;
        if(q >= MOD) q %= MOD;
        ans = ans * q;
        if(ans >= MOD) ans %= MOD;
    }
    return ans ;
}
int main()
{
    cin >> t;
    while(t--){
        cin >> n >> m;
        init(n);
        rep(i,0,m-1){
            int u , v;
            cin >> u >> v;
            u-- , v --;
            add(u,v);
            add(v,u);
        }
        cout << solve() << endl;

    }


    return 0;
}

题意:1 2 3 ,每个点给一个值,要求相邻两点权值加和为奇数,求方案数

题解:假设有两个数1 2的话,很明显,如果存在合理方案,那么就肯定为两种,1和2换一下即可,因为这里多了一个3,所以,

如果权值为奇数的为cnt1个,偶数的cnt2个,此时方案数为2^cnt1, 然后奇偶一换,方案数为2^cnt2

所以方案总数为2cnt1+2cnt2,因为没说是所有的点联通,所以每块乘起来即可,若遇到不符合标记一下即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod=998244353; 
const int N=3e5+10;
#define pb push_back
int n,m,vis[N];
vector<int> v[N];
int cnt1,cnt2;
int flag;
void dfs(int u)
{
	if(vis[u]==1) cnt1++;
	else cnt2++;
	for(int i=0;i<v[u].size();i++)
	{
		int to=v[u][i];
		if(vis[to])
		{
			if(vis[to]==vis[u]) flag=1;  //   遇见不符合 标记
		}
		else
		{
			vis[to]=3-vis[u];
			dfs(to);
		}
	}
}
ll ksm(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int x,y;
		scanf("%d%d",&n,&m);
		for(int i=0;i<=n;i++)vis[i]=0,v[i].clear();
		flag=0;
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&x,&y);
			v[x].pb(y);
			v[y].pb(x); 
		}
		ll ans=1;
		for(int i=1;i<=n;i++)
		{
			if(vis[i]) continue;
			vis[i]=1;
			cnt1=cnt2=0;
			dfs(i);
			if(flag) break;
			ans=(ans*(ksm(2,cnt1)+ksm(2,cnt2))%mod)%mod;
		}
		if(flag) cout<<"0\n";
		else cout<<ans<<endl;
	}
	return 0;
}

题意概括: 给一个无向图,要求在每个结点填入 1,2,3
三个数的其中一个,如果每条边相连的两个结点的权值之和为奇数,则当前的填数方案是满足条件的;

询问有多少种填数方案,如果不存在则输出0;

解题思路: 因为只有 1, 2, 3 三个数,其中有两个奇数,
一个偶数。为了满足题目的条件,我们的填入规则肯定是在一条路径上奇偶奇偶…这样填入数字,保证相连两点的和为奇数。

那么就有该路径是先填奇数还是先填偶数之分了:

如果是在该路径上的第一点填入奇数,那么由这个点出发的路径的偶数点肯定是要填偶数,那么我们统计填入奇数的点(即奇数点)的个数 p
,每个点可填两种数,方案有 2 的 p 次方种。

如果在该路径的第一点填入偶数,那么偶数点要填奇数(1或者3),统计填入奇数的点(即偶数点)的个数 x,每个点有两种选择,方案数为 2 的 x
次方。

那么总的方案数就是上面两种情况的方案数之和。

由此,我们发现统计路径的奇数点和偶数点,分别以他们求2次幂之和,就是以当前点出发所能经过路径的方案数了。枚举一遍起点,求出总的方案数就是答案。

那么无解的情况呢,就是存在起点和终点奇偶性相同的环(DFS过程中判断一下即可)。

注意点:

一、2次幂可先预处理

二、一开始敲得静态邻接表版本要注意初始化得方式 for循环可过,效率客观(比vector版本快些)。memset初始化超时。

三、vector版本的for循环初始化即可。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
const LL MOD = 998244353;
int N, M, p, s;
LL ans;
const int MAXN = 3e5+10;
int book[MAXN];
LL pw[MAXN+1];
bool con;
//bool vis[MAXN];
struct EDGE
{
    int v, nxt;
}edge[MAXN<<1];
int head[MAXN], cnt;

void add(int u, int v)
{
    edge[cnt].v = v;
    edge[cnt].nxt = head[u];
    head[u] = cnt++;
}

void init()
{
//    memset(book, -1, sizeof(book));
//    memset(head, -1, sizeof(head));
    cnt = 0;
    con = true;
    ans = 1LL;
}

void dfs(int now, int flg)
{
    if(flg == 1) p++;
    else s++;

    book[now] = flg;
    int v;
    for(int i = head[now]; i != -1; i = edge[i].nxt){
        v = edge[i].v;
        if(book[v] == -1){
            dfs(v, 1-flg);
        }
        else if(book[v] == flg) {con = 0; return;}
    }
}

int main()
{
    int T_case, u, v;
    scanf("%d", &T_case);
    pw[0] = 1;
    for(int i = 1; i < MAXN; i++){
        pw[i] = (pw[i-1]*2)%MOD;
    }
    memset(head, -1, sizeof(head));
    memset(book, -1, sizeof(book));
    while(T_case--)
    {
        init();
        scanf("%d%d", &N, &M);
        for(int i = 1; i <= M; i++){
            scanf("%d %d", &u, &v);
            add(u, v);
            add(v, u);
        }
        for(int st = 1; st <= N; st++){
            if(book[st] != -1) continue;
            else if(book[st] == -1){
                p = 0, s = 0;
                dfs(st, 1);
                ans = ans*(pw[p]+pw[s])%MOD;
            }
        }

        if(con) printf("%I64d\n", ans);
        else printf("0\n");

        for(int i = 0; i <= N; i++){
            head[i] = -1;
            book[i] = -1;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值