训练题解之5.6

E UVA-10766 Organising the Organisation

题目大意:现有n个点,m条边,k为根节点,每条边代表边的两点有矛盾,不能相连,问存在多少生成树。
题解:典型的生成树计数,首先处理出补图,然后跑一边生成树计数,根节点对本题无用,注意会出现重边。
生成树计数主要依靠:基尔霍夫矩阵树定理。(证明略)
基尔霍夫矩阵:对角线上的点的值为每个点的度数,其他点的值若i与j相连则为-1。
无向图的生成树数目:任意n-1阶行列式的绝对值。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<set>
#include<map>
#include<vector>
#include<cstdio>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=3e2+100;
//ll mod=1;
ll A[maxn][maxn];
ll B[maxn][maxn];
ll determinant(int n){
    ll res=1;
    for(int i=1;i<=n;i++){
        if(!B[i][i]){
            bool flag=false;
            for(int j=i+1;j<=n;j++){
                if(B[j][i]){
                    flag = true;
                    for(int k=i;k<=n;k++){
                        swap(B[i][k],B[j][k]);
                    }
                    res=-res;
                    break;
                }
            }
            if(! flag)
            return 0;
        }
        for(int j=i+1;j<=n;j ++){
            while(B[j][i]){
                ll t=B[i][i]/B[j][i];
                for(int k=i;k<=n;k ++){
                    B[i][k]=B[i][k]-t*B[j][k];
                    swap(B[i][k],B[j][k]);
                }
                res=-res;
            }
        }
        res*= B[i][i];
    }
    return res;
}
int main(){
    int n, m, k;
    while(~ scanf("%d %d %d", &n, &m, &k)){   
        memset(A,0,sizeof(A));
        memset(B,0,sizeof(B));
        for(int i=1;i<=m;i ++){
            int a,b;
            scanf("%d %d",&a,&b);
            A[a][b]=A[b][a]=1;
        }
        for(int i=1;i<=n;i ++){
            for(int j=1;j<=n;j++){
                if(i!=j&&!A[i][j]){
                    B[i][i]++;
                    B[i][j]=-1; 
                }
            }
        }
        n=n-1;
        ll ans=determinant(n); 
        printf("%lld\n", ans);
    }
    return 0;
}

F codeforces 557D Vitaly and Cycle

题目大意:一张n个点,m条边的无向图,求出至少需要添加几条边使得图中存在奇环,并且输出方案数。
题解:对于奇环的相关问题首先想到的就是二分图染色,由于最少三边就可以形成一个奇数环,所以添加的边数最多为3。可以分类讨论添加边的个数。

当添加的边数为0时
此时图中存在奇环。

当添加的边数为1时
此时的图可能是树或者存在偶环,根据二分图染色,在两个黑点之间或者两个白点之间连一条边可以构成奇环。所以在每个连通块中计算得到和为方案数。
ans = ∑(white[i]−1)∗white[i]/2+(black[i]−1)∗black[i]/2

当添加的边数为2时
图中所有点的度数<=1,所以每条边所对应的两个点分别与其他相同的一个点相连即可构成奇环。
ans=m*(n-2)

当添加边数为3时
此时m为0,可以使用组合数计算,随机抽出三个点的方案数。
ans=n*(n-1)*(n-2)/6

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<stack> 
#include<map>
#include<deque>
using namespace std;
typedef long long ll;
#define eps 1e-8
#define pi acos(-1.0)
template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F&&(num=-num);
}
const int maxn=2e5+100;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
inline ll mul(ll a,ll b){
    ll c=a*b-(ll)((long double)a*b/mod+0.5)*mod;
    return c<0?c+mod:c;
}
//void print(__int128 x){
//    if(x<0){
//        putchar('-');
//        x = -x;
//    }
//    if(x>9) print(x/10);
//    putchar(x%10+'0');
//}
int head[maxn],Next[maxn],ver[maxn],tot;
void add(int x,int y){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
}
int v[maxn],d[maxn],fa[maxn];
ll w[maxn],b[maxn];
bool dfs(int x,int color,int k){
	v[x]=color;
	fa[x]=k;
	if(color&1) ++b[k];
	else ++w[k];
	for(int i=head[x];i;i=Next[i]){
		int y=ver[i];
		if(v[y]==0){
			if(!dfs(y,3-color,k)) return 0;	
		}
		else if(v[y]==color) return 0;
	}
	return 1;
}
int main(){
	tot=1;
	int n,m,flag=1;
	read(n);read(m);
	for(int i=1;i<=m;i++){
		int x,y;
		read(x);read(y);
		add(x,y);
		add(y,x);
		d[x]++;
		d[y]++;
		if(d[x]>1||d[y]>1) flag=0;
	}
	if(!m){
		printf("3 %lld\n",(ll)n*(n-1)*(n-2)/6);
		return 0;
	}
	if(flag){
		printf("2 %lld\n",(ll)m*(n-2));
		return 0;
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(v[i]==0){
			int k=dfs(i,1,++cnt);
			if(!k){
				printf("0 1\n");
				return 0;
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=cnt;i++){
		ans+=(w[i]-1)*w[i]/2+(b[i]-1)*b[i]/2;
	}
	printf("1 %lld\n",ans);
}

F CodeForces - 786E ALT

题目大意:给出 n 个点的树和 m 个人,第 i 个人会从树上的最短距离从点 xi 到点 yi ,可以选择在边上放一只狗或者给某个人一只狗,要满足每个人在每条路径上走都要有狗的陪伴,输出需要狗的最少数量,以及方案。
题解:首先对于这种题型熟悉的话,是最小割模型,那么首先是建图。设有超级源点 S 和超级汇点 T ,从S向每个人连一条流量为1的边,第i个人向从 xi 到 yi 经过的每条边连一条流量为 inf 的边,最后每条边向 T 连一条流量为 inf 的边。
但是这样建边会建成N2条边,跑最大流一定会 T ,所以可以用线段树优化建图的方法减少边的数量。
简单介绍一下如何用线段树优化建图
在这里插入图片描述
这样就可以减少边的数量,同时因为图为一棵树,可以用树链剖分维护。具体树链剖分中如何建图请看代码。
最后要输出方案,可以从 S 开始 dfs ,不经过满流的边,标记到 S 的点为到S的,没访问的点为T。当第 i 个人在S时,该边有狗,T 时为他自己带着狗。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<stack> 
#include<map>
#include<deque>
using namespace std;
typedef long long ll;
#define eps 1e-8
#define pi acos(-1.0)
template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F&&(num=-num);
}
const int maxn=1e6+100;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
inline ll mul(ll a,ll b){
    ll c=a*b-(ll)((long double)a*b/mod+0.5)*mod;
    return c<0?c+mod:c;
}
//void print(__int128 x){
//    if(x<0){
//        putchar('-');
//        x = -x;
//    }
//    if(x>9) print(x/10);
//    putchar(x%10+'0');
//}
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
int head1[maxn],ver1[maxn],edge1[maxn],Next1[maxn],d[maxn];
int n,m,s,t,tot1,maxflow;
vector<int>ans1,ans2;
queue<int> q;
void add_e(int x,int y,int z){
	ver1[++tot1]=y,edge1[tot1]=z,Next1[tot1]=head1[x],head1[x]=tot1;
	ver1[++tot1]=x,edge1[tot1]=0,Next1[tot1]=head1[y],head1[y]=tot1;
} 
bool bfs(){
	memset(d,0,sizeof d);
	while(!q.empty()) q.pop();
	q.push(s);
	d[s]=1;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=head1[x];i;i=Next1[i]){
			if(edge1[i]&&!d[ver1[i]]){
				q.push(ver1[i]);
				d[ver1[i]]=d[x]+1;
				if(ver1[i]==t) return 1;
			}
		}
	}
	return 0;
}
int dinic(int x,int flow){
	if(x==t) return flow;
	int rest=flow,k;
	for(int i=head1[x];i&&rest;i=Next1[i]){
		if(edge1[i]&&d[ver1[i]]==d[x]+1){
			k=dinic(ver1[i],min(rest,edge1[i]));
			if(!k) d[ver1[i]]=0;
			edge1[i]-=k;
			edge1[i^1]+=k;
			rest-=k;
		}
	}
	return flow-rest;
}
int tot,head[maxn],Next[maxn],ver[maxn],wt[maxn],maxx[maxn];
//链式前向星数组,w[]、wt[]初始点权数组 
int a[maxn<<2],lazy[maxn<<2],idd[maxn];
map<pair<int, int>, int> w;
//线段树数组、lazy操作 
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 
//son[]重儿子编号,id[]新编号,fa[]父亲节点,cnt dfs_clock/dfs序,dep[]深度,siz[]子树大小,top[]当前链顶端节点 
int res=0;
//查询答案 

inline void add(int x,int y){//链式前向星加边 
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
//-------------------------------------- 以下为线段树 
int ecnt=0;
inline void build(int rt,int l,int r){
	idd[rt]=++ecnt;
//	printf("%d\n",ecnt);
    if(l==r){
        a[wt[l]]=idd[rt];
        add_e(idd[rt],t,1);
        return;
    }
    build(lson);
    build(rson);
    add_e(idd[rt],idd[rt<<1],inf);
    add_e(idd[rt],idd[rt<<1|1],inf);
}

inline void update(int rt,int l,int r,int L,int R,int k){
//    printf("debug\n"); 
    if(l>R||r<L) return;
	if(L<=l&&r<=R){
        add_e(k,idd[rt],inf);
        return ;
    }
    update(lson,L,R,k);
    update(rson,L,R,k);
}
//---------------------------------以上为线段树 
inline void updRange(int x,int y,int k){//同上 
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    update(1,1,n,id[x]+1,id[y],k);
}

inline void dfs1(int x,int f,int deep){//x当前节点,f父亲,deep深度 
    dep[x]=deep;//标记每个点的深度 
    fa[x]=f;//标记每个点的父亲 
    siz[x]=1;//标记每个非叶子节点的子树大小 
    int maxson=-1;//记录重儿子的儿子数 
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==f)continue;//若为父亲则continue 
        dfs1(y,x,deep+1);//dfs其儿子 
        siz[x]+=siz[y];//把它的儿子数加到它身上 
        if(siz[y]>maxson)son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号 
    }
}

inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点 
    id[x]=++cnt;//标记每个点的新编号 
    wt[cnt]=w[make_pair(x,fa[x])];//把每个点的初始值赋到新编号上来 
    top[x]=topf;//这个点所在链的顶端 
    if(!son[x])return;//如果没有儿子则返回 
    dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理 
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa[x]||y==son[x])continue;
        dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
    }
}
int b[maxn],vis[maxn];
void dfs_get(int x) {
    vis[x]=1;
    for(int i=head1[x];i;i=Next1[i]){
        if(edge1[i]&&!vis[ver1[i]]) {
            dfs_get(ver1[i]);
        }
    }
}
int main(){
	tot=0;
	tot1=1;
	read(n);read(m);
	for(int i=1;i<n;i++){
		int x,y;
		read(x);read(y);
		add(x,y);
		add(y,x);
		w[make_pair(x,y)]=w[make_pair(y,x)]=i;
	}
	s=++ecnt;
	t=++ecnt;
    dfs1(1,0,1);
    dfs2(1,1);
    build(1,1,n);
    for(int i=1;i<=m;i++){
    	int x,y;
    	read(x);read(y);
    	b[i]=++ecnt;
    	add_e(s,b[i],1);
    	updRange(x,y,b[i]);
	}
	int flow=0;
	while(bfs()){
		while(flow=dinic(s,inf)) maxflow+=flow; 
	}
	printf("%d\n",maxflow);
	dfs_get(s);
    for(int i=1;i<=m;++i) {
        if(!vis[b[i]]){
            ans1.push_back(i);
        }
    }
    for(int i=1;i<n;++i) {
        if(vis[a[i]]) {
            ans2.push_back(i);
        }
    }
    printf("%d ",(int)(ans1.size()));
    for(int i =0;i<ans1.size();++i) {
        printf("%d ",ans1[i]);
    }
    printf("\n%d ",(int)(ans2.size()));
    for(int i=0;i<ans2.size();++i) {
        printf("%d ",ans2[i]);
    }
    puts("");
    return 0;
}

H deForces - 1174D Ehab and the Expected XOR Problem

题目大意:构造出一个的序列,满足下列条件:

  1. 1 < = ai < 2n
  2. 任意区间的异或和不能为 0 或者 x;
  3. 序列的长度最大

题解:当我们知道该序列的异或前缀和,那么任意区间都可以被前缀和表示出来,因为 a ^ b =c => a ^ c =b.那么问题就转化为了最多可以有多少个数两两之间异或值不为 0 或者 x ,所以可以从 1 到 2n 遍历,并且去掉 i ^ x 。
最后序列的值可以用 a[ i ] ^ a[ i- 1 ] 得出。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<stack> 
#include<map>
#include<deque>
using namespace std;
typedef long long ll;
#define eps 1e-8
#define pi acos(-1.0)
template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F&&(num=-num);
}
const int maxn=(1<<18)+10;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
inline ll mul(ll a,ll b){
    ll c=a*b-(ll)((long double)a*b/mod+0.5)*mod;
    return c<0?c+mod:c;
}
//void print(__int128 x){
//    if(x<0){
//        putchar('-');
//        x = -x;
//    }
//    if(x>9) print(x/10);
//    putchar(x%10+'0');
//}
int v[maxn],a[maxn];
int main(){
	int n,x;
	read(n);read(x);
	int nn=(1<<n)-1;
	v[0]=v[x]=1;
	int k=0;
	for(int i=1;i<=nn;i++){
		if(v[i]) continue;
		v[i]=v[i^x]=1;
		a[++k]=i;
	}
	printf("%d\n",k);
	for(int i=1;i<=k;i++){
		printf("%d%c",a[i]^a[i-1],"\n "[i!=k]);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值