1. 程式人生 > 實用技巧 >11.01

11.01

有兩道題調了很久(都差不多有一兩個小時)都沒調出來,明天(10.30)準備和題解對拍了……

圖論:割點、橋、點雙、邊雙

一些 NOI Online

POJ1144 網路

題意:

求無向圖割點數量。 \((1\le N<100)\)

程式碼:

#include <cstdio>
#include <vector>
#include <algorithm>
#define y e[x][i]
using namespace std;
const int N=105;
int n,s,dfn[N],low[N];
bool cut[N];
vector<int> e[N];
void tarjan(int x,int rt){
	int&l=low[x],cnt=0;dfn[x]=l=++s;
	for(int i=0;i<e[x].size();i++)
		if(!dfn[y]){
			tarjan(y,rt);cnt++;l=min(l,low[y]);
			if(low[y]>=dfn[x]) cut[x]=true;
		}
		else l=min(l,dfn[y]);
	if(x==rt&&cnt==1) cut[x]=false;
}
void solve(){
	s=0;
	for(int i=1;i<=n;i++) {e[i].clear();dfn[i]=low[i]=cut[i]=0;}
	for(int u,v;~scanf("%d",&u)&&u;)
		while(getchar()!='\n') {scanf("%d",&v);e[u].push_back(v);e[v].push_back(u);}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i,i);
	printf("%d\n",count(cut+1,cut+n+1,true));
}
signed main(){
	while(~scanf("%d",&n)&&n) solve();
	return 0;
}

POJ2117 Electricity

題意:

求一個 \(N\) 個點 \(M\) 條邊的無向圖圖刪除一個點之後,最多有多少個連通分量。 \((1\le N\le10^4)\)

程式碼:

#include <cstdio>
#include <vector>
#include <algorithm>
#define y e[x][i]
using namespace std;
const int N=10005;
int n,m,s,cnt,dfn[N],low[N],cut[N];
vector<int> e[N];
void tarjan(int x,int rt){
	int&l=low[x];dfn[x]=l=++s;
	for(int i=0;i<e[x].size();i++)
		if(!dfn[y]){
			tarjan(y,rt);l=min(l,low[y]);
			if(low[y]>=dfn[x]) cut[x]++;
		}
		else l=min(l,dfn[y]);
	if(x==rt) cut[x]--;
}
void solve(){
	cnt=s=0;
	for(int i=0;i<n;i++) {e[i].clear();dfn[i]=low[i]=cut[i]=0;}
	while(m--) {int u,v;scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}
	for(int i=0;i<n;i++)
		if(!dfn[i]) {cnt++;tarjan(i,i);}
	printf("%d\n",cnt+*max_element(cut,cut+n));
}
signed main(){
	while(~scanf("%d%d",&n,&m)&&n) solve();
	return 0;
}

HDU4587 TWO NODES

題意:

求無向圖中刪除任意兩個點之後所能獲得的獨立連通分量個數的最大值。 \((3\le N,M\le5000)\)

程式碼:

#include <cstdio>
#include <vector>
#include <algorithm>
#define y e[x][i]
using namespace std;
const int N=5005;
int n,m,s,d,cnt,ans,dfn[N],low[N],cut[N];
vector<int> e[N];
void tarjan(int x,int rt){
	int&l=low[x];dfn[x]=l=++s;
	for(int i=0;i<e[x].size();i++)
		if(y!=d){
			if(!dfn[y]){
				tarjan(y,rt);l=min(l,low[y]);
				if(low[y]>=dfn[x]) cut[x]++;
			}
			else l=min(l,dfn[y]);
		}
	if(x==rt) cut[x]--;
}
void solve(){
	ans=0;
	for(int i=0;i<n;i++) e[i].clear();
	while(m--) {int u,v;scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}
	for(d=0;d<n;d++){
		fill_n(dfn,n,0);fill_n(cut,n,0);cut[d]=-1;cnt=s=0;
		for(int i=0;i<n;i++)
			if(i!=d&&!dfn[i]) {cnt++;tarjan(i,i);}
		ans=max(ans,cnt+*max_element(cut,cut+n));
	}
	printf("%d\n",ans);
}
signed main(){
	while(~scanf("%d%d",&n,&m)&&n) solve();
	return 0;
}

好玄學哦,之前寫了一個時間複雜度嚴格 \(N^2\) 的結果 T 了,換成這個時間複雜度有點問題的寫法才過。

POJ3177 分離的路徑

題意:

求無向圖最少加幾條邊使其成為邊雙。 \((1\le N\le5000,1\le M\le10^4)\)

程式碼:

#include <bits/stdc++.h>
#define id e[x][i]
#define y v[id]
using namespace std;
const int N=5005,M=20005;
int n,m,s1,s2,top,ans,s[N],d[N],dfn[N],low[N],bel[N],v[M];
bool vis[M];
vector<int> e[N];
void tarjan(int x){
	int&l=low[x];l=dfn[x]=++s1;s[++top]=x;
	for(int i=0;i<e[x].size();i++)
		if(!vis[id]){
			vis[id^1]=true;
			if(!dfn[y]) {tarjan(y);l=min(l,low[y]);}
			else l=min(l,dfn[y]);
		}
	if(l!=dfn[x]) return;bel[x]=++s2;
	for(int i;i!=x;) bel[i=s[top--]]=s2;
}
signed main(){
	scanf("%d%d",&n,&m);
	for(int i=0,p,q;i<m;i++) {p=i<<1;q=i<<1|1;scanf("%d%d",&v[q],&v[p]);e[v[q]].push_back(p);e[v[p]].push_back(q);}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
	for(int x=1;x<=n;x++)
		for(int i=0;i<e[x].size();i++)
			if(bel[x]!=bel[y]) {d[bel[x]]++;d[bel[y]]++;}
	for(int i=1;i<=s2;i++) ans+=d[i]==2;
	printf("%d\n",(ans+1)/2);
	return 0;
}

POJ3352 Road Construction

除了無重邊之類的毒瘤東西和上面一樣,不過可以用 low 判,儘管這樣求邊雙是假的但是求葉子節點並沒什麼問題。

LA5135 井下礦工

題意:

給一張聯通無向圖選定最少的點使刪除任意點後其餘點都可以和至少一個被選點聯通。 \((1\le N\le50000)\) ,答案在 long long 範圍內。

程式碼:

啊啊啊啊啊啊啊啊啊怎麼都沒調出來, udebug 發現垃圾資料直接給你一個樣例。求 debug 。

#include <bits/stdc++.h>
#define y e[x][i]
using namespace std;
typedef long long ll;
const int N=50005;
int t,n,m,sum,top,ans1,s[N],dfn[N],low[N],cut[N];
ll ans2;
vector<int> e[N];
vector<vector<int> > b;
void tarjan(int x){
	int&l=low[x],ch=0;l=dfn[x]=++sum;s[++top]=x;
	for(int i=0;i<e[x].size();i++)
		if(!dfn[y]){
			tarjan(y);l=min(l,low[y]);ch++;
			if(low[y]>=dfn[x]){
				vector<int> bcc;bcc.push_back(x);cut[x]=1;
				while(s[top+1]!=y) bcc.push_back(s[top--]);
				b.push_back(bcc);
			}
		}
		else l=min(l,dfn[y]);
	if(x==1&&ch==1) cut[x]=0;
}
void solve(){
	b.clear();ans1=sum=top=0;ans2=1;
	for(int i=1;i<=m;i++) {e[i].clear();s[i]=dfn[i]=cut[i]=0;}
	while(m--) {int u,v;scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}
	tarjan(1);
	if(b.size()==1) {printf("Case %d: 2 %lld\n",++t,1ll*b[0].size()*(b[0].size()-1)/2);return;}
	for(int i=0;i<b.size();i++){
		int cnt=0;
		for(int j=0;j<b[i].size();j++) cnt+=cut[b[i][j]];
		if(cnt==1) {ans1++;ans2*=b[i].size()-1;}
	}
	printf("Case %d: %d %lld\n",++t,ans1,ans2);
}
signed main(){
	while(~scanf("%d",&m)&&m) solve();
	return 0;
}

HDU3394 Railway

題意:

求無向圖橋的個數和在多於一個環中的邊的個數。 \((1\le N\le10^4,1\le M\le10^5)\)

程式碼:

靜態查錯不出來,討論裡的幾個資料也沒用……求 debug 。

#include <bits/stdc++.h>
#define y e[x][i]
using namespace std;
const int N=10005;
int n,m,sum,top,ans1,ans2,s[N],dfn[N],low[N];
bool now[N];
vector<int> e[N];
void tarjan(int x){
    int&l=low[x];l=dfn[x]=++sum;s[++top]=x;
    for(int i=0;i<e[x].size();i++)
        if(!dfn[y]){
            tarjan(y);l=min(l,low[y]);
            if(low[y]>=dfn[x]){
                vector<int> b;b.push_back(x);
                while(s[top+1]!=y) b.push_back(s[top--]);
                int cnt=0,sz=b.size();
                for(int j=0;j<sz;j++) now[b[j]]=true;
                for(int j=0;j<sz;j++)
                    for(int k=0;k<e[b[j]].size();k++)
                        if(now[e[b[j]][k]]) cnt++;
                cnt/=2;if(sz>cnt) ans1+=cnt;if(sz<cnt) ans2+=cnt;
                for(int j=0;j<sz;j++) now[b[j]]=false;
            }
        }
        else l=min(l,dfn[y]);
}
void solve(){
    sum=top=ans1=ans2=0;
    for(int i=0;i<n;i++) {e[i].clear();s[i]=dfn[i]=0;}
    while(m--) {int u,v;scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}
    for(int i=0;i<n;i++)
        if(!dfn[i]) tarjan(i);
    printf("%d %d\n",ans1,ans2);
}
signed main(){
    while(~scanf("%d%d",&n,&m)&&n) solve();
    return 0;
}

[POI2008]BLO-Blockade

題意:

一個連通圖,對每個點求去掉這個點會有多少對有序點變的不連通(包括這個點)。 \((1\le N\le10^5,1\le M\le5\times10^5)\)

程式碼:

#include <bits/stdc++.h>
#define y e[x][i]
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,sum,dfn[N],low[N],sz[N];
ll ans[N];
vector<int> e[N];
void tarjan(int x){
	int&l=low[x],&s=sz[x]=1,c=0;l=dfn[x]=++sum;
	for(int i=0;i<e[x].size();i++)
		if(!dfn[y]){
			tarjan(y);l=min(l,low[y]);s+=sz[y];
			if(low[y]>=dfn[x]) {ans[x]+=1ll*sz[y]*c;c+=sz[y];}
		}
		else l=min(l,dfn[y]);
	ans[x]+=1ll*(n-c-1)*c;
}
signed main(){
	scanf("%d%d",&n,&m);
	while(m--) {int u,v;scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}
	tarjan(1);
	for(int i=1;i<=n;i++) printf("%lld\n",(ans[i]+n-1)*2);
	return 0;
}

SP2878 KNIGHTS - Knights of the Round Table

題意:

\(N\) 個點放成一個長度為奇數且不為一的環,有 \(M\) 對點不能相鄰,求有哪些點一定無法被放進環中。 \((1\le N\le10^3,1\le M\le10^6)\)

題解:

考慮建出這個圖的補圖,則有邊相鄰的點才可以放在一起,則問題轉化為求這個點是否被包含在奇環中。

如果不在一個點雙裡,就肯定不會有公共的環,因此每個點雙內部考慮。

只要對每個點雙進行二分圖染色,如果有奇環點雙內每個點都可以被放進環中。

時間複雜度: \(O(M+N^2)\)

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m,sum,top,s[N],dfn[N],low[N],c[N];
bool flag,vis[N],ans[N],e[N][N];
vector<vector<int> > b;
void tarjan(int x){
	int&l=low[x];l=dfn[x]=++sum;s[++top]=x;
	for(int i=1;i<=n;i++)
		if(x!=i&&e[x][i]){
			if(!dfn[i]){
				tarjan(i);l=min(l,low[i]);
				if(low[i]>=dfn[x]){
					vector<int> bcc;bcc.push_back(x);
					while(s[top+1]!=i) bcc.push_back(s[top--]);
					b.push_back(bcc);
				}
			}
			else l=min(l,dfn[i]);
		}
}
void dfs(int x,int v){
	c[x]=v;
	for(int i=1;i<=n&&!flag;i++)
		if(vis[i]&&x!=i&&e[x][i]){
			if(c[i]==v) flag=1;
			if(!c[i]) dfs(i,3-v);
		}
}
void solve(){
	sum=top=0;b.clear();
	for(int i=1;i<=n;i++) {s[i]=dfn[i]=ans[i]=0;fill_n(e[i]+1,n,1);}
	while(m--) {int u,v;scanf("%d%d",&u,&v);e[u][v]=e[v][u]=0;}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
	for(int i=0;i<b.size();i++){
		fill_n(vis+1,n,0);fill_n(c+1,n,0);flag=0;
		for(int j=0;j<b[i].size();j++) vis[b[i][j]]=1;
		dfs(b[i][0],1);
		for(int j=0;j<b[i].size();j++){
			vis[b[i][j]]=0;
			if(flag) ans[b[i][j]]=1;
		}
	}
	printf("%d\n",count(ans+1,ans+n+1,0));
}
signed main(){
	while(~scanf("%d%d",&n,&m)&&n&&m) solve();
	return 0;
}

NOI Online #1 提高組 氣泡排序

題意:

給定一個長為 \(N\) 的排列,有 \(Q\) 次操作:

  • 1 x :將排列的第 \(x\) 個數和第 \(x+1\) 個數交換。

  • 2 k :求經過 \(k\) 輪氣泡排序的逆序對個數
    \((1\le N,Q\le 2\times10^5,0\le k<2^ {31})\)

程式碼:

#include <bits/stdc++.h>
#define ii inline
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m,a[N],b[N],c[N];
ll ans,tree[N];
ii int lb(int x) {return x&(-x);}
ii void add(int x,ll v) {while(x<=n) {tree[x]+=v;x+=lb(x);}}
ii ll ask(int x) {ll res=0;while(x) {res+=tree[x];x-=lb(x);}return res;}
signed main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {scanf("%d",&a[i]);b[i]=i-1-ask(a[i]);ans+=b[i];c[b[i]]++;add(a[i],1);}
	memset(tree,0,sizeof(tree));add(1,ans);
	for(int i=0,tot=0;i<n;i++) {tot+=c[i];add(i+2,tot-n);}
	while(m--){
		int opt,x;scanf("%d%d",&opt,&x);x=min(x,n-1);
		if(opt==1){
			swap(a[x],a[x+1]);swap(b[x],b[x+1]);
			if(a[x]>a[x+1]) {add(1,1);add(b[x+1]+2,-1);b[x+1]++;}
			else {add(1,-1);b[x]--;add(b[x]+2,1);}
		}
		else printf("%lld\n",ask(x+1));
	}
	return 0;
}

NOI Online #3 提高組 魔法值

題意:

\(N\) 個城市,起初有魔法值 \(F_i\)\(M\) 條有向邊,每天每個城市的魔法值會變成它的入點魔法值異或和, \(Q\) 次詢問,每次詢問 \(A_i\) 天城市一的魔法值。 \((1≤N,Q≤100,1\le M\le\frac{N(N-1)}{2},1\le A_i\le 2^{32},0\le F_i\le2^{32})\)

程式碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=105,L=32;
int n,m,q;
struct mat{
	int x,y;ll b[N][N];
	mat operator * (const mat&t) const {
		mat res;res.x=x;res.y=t.y;
		for(int i=1;i<=x;i++)
			for(int j=1;j<=t.y;j++){
				res.b[i][j]=0;
				for(int k=1;k<=y;k++) res.b[i][j]^=b[i][k]*t.b[k][j];
			}
		return res;
	}
}g,f[L];
signed main(){
	scanf("%d%d%d",&n,&m,&q);g.x=1;f[0].x=f[0].y=g.y=n;
	for(int i=1;i<=n;i++) scanf("%lld",&g.b[1][i]);
	while(m--) {int u,v;scanf("%d%d",&u,&v);f[0].b[u][v]=f[0].b[v][u]=1;}
	for(int i=1;i<L;i++) f[i]=f[i-1]*f[i-1];
	while(q--){
		int x;scanf("%lld",&x);mat ans=g;
		for(ll i=0;i<L;i++)
			if(x>>i&1ll) ans=ans*f[i];
		printf("%lld\n",ans.b[1][1]);
	}
	return 0;
}