2019.10.13作业总结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/wljoi/article/details/102556084

主要知识点:差分约束系统,Tarjan算法

T1:小K的农场

传送门

解析:差分约束系统模板题,注意用dfs的spfa来优化找负环的时间。

#include<bits/stdc++.h>
#define inf 2147483647
using namespace std;
inline int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void Write(int x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9){
		Write(x/10);
	}
	putchar(x%10+'0');
}
int first[200005],nxt[200005],to[200005],w[200005],tot=0;
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
int n,m,d[200005],inq[200005],cnt[200005];
bool spfa(int u){
	inq[u]=1;
	for(int e=first[u];e;e=nxt[e]){
		int v=to[e];
		if(d[v]>d[u]+w[e]){
			d[v]=d[u]+w[e];
			if(inq[v])  return false;
			if(!spfa(v))  return false;
		}
	}
	inq[u]=0;
	return true;
}
signed main(){
	n=Read(),m=Read();
	int opt,a,b,c;
	for(int i=1;i<=n;i++){
		Add(0,i,1);
	}
	for(int i=1;i<=m;i++){
		opt=Read();
		if(opt==3){
			a=Read(),b=Read();
			Add(a,b,0);
			Add(b,a,0);
		}
		if(opt==2){
			a=Read(),b=Read(),c=Read();
			Add(b,a,c);
		}
		if(opt==1){
			a=Read(),b=Read(),c=Read();
			Add(a,b,-c);
		}
	}
	memset(d,10,sizeof(d));
	d[0]=0;
	if(spfa(0))  cout<<"Yes\n";
	else  cout<<"No\n";
}

T2:账本核算(原HNOI2005狡猾的商人)

传送门

解析:思路很巧妙,将 f i + f i + 1 + . . . + f j f_i+f_{i+1}+...+f_j 转化为前缀和 s j s i 1 s_j-s_{i-1} ,然后将 s j s i 1 = z s_j-s_{i-1}=z 转成
{ s j s i 1 z s j s i 1 z \left\{ \begin{aligned} s_j-s_{i-1}\le z\\ s_j-s_{i-1}\ge z\\ \end{aligned} \right.
然后用差分约束系统做即可。

#include<bits/stdc++.h>
using namespace std;
inline int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void Write(int x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9){
		Write(x/10);
	}
	putchar(x%10+'0');
}
int first[50005],nxt[50005],to[50005],w[50005],tot=0;
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
int d[10005],vis[10005],so[10005];
bool spfa(int u){
	vis[u]=1;
	so[u]=1;
	for(int e=first[u];e;e=nxt[e]){
		int v=to[e];
		if(d[v]>d[u]+w[e]){
			d[v]=d[u]+w[e];
			if(vis[v])  return false;
			if(!spfa(v))  return false;
		}
	}
	vis[u]=0;
	return true;
}
signed main(){
	int T=Read();
	while(T--){
		memset(first,0,sizeof(first));
		memset(to,0,sizeof(to));
		memset(nxt,0,sizeof(nxt));
		memset(w,0,sizeof(w));
		memset(vis,0,sizeof(vis));
		memset(so,0,sizeof(so));
		tot=0;
		int n=Read(),m=Read();
		for(int i=1;i<=m;i++){
			int x=Read(),y=Read(),z=Read();
			Add(x-1,y,-z);
			Add(y,x-1,z);
		}
		bool flag=true;
		memset(d,10,sizeof(d));
		for(int i=1;i<=n;i++){
			if(!so[i])  d[i]=0,flag&=spfa(i);
		}
		if(!flag)  cout<<"false\n";
		else  cout<<"true\n";
	}
}

T3:魔法石(原CF652E)

传送门

解析:由于是无向图,所以我们不能使用强连通分量,由于我们维护的是边的信息,所以我们可以采用边双连通分量来维护。缩点后,图就变为一棵树,然后在树上dfs就可以了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<stack>
const int MAXN = 3e5 + 5;
const int MAXM = 3e5 + 5;
using namespace std;

inline void chk_min(int &a,int b){ if(a>b) a=b;}

struct Edge
{
    int next,from,to,w;
}e[MAXM*2];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
    ++etot;
    e[etot]=(Edge){ head[u],u,v,w};
    head[u] = etot;
}

stack<int> s;
int dfn[MAXN],low[MAXN],cnt=0;
bool vis[MAXN];
int clr[MAXN],ccnt=0;
void tarjan(int u,int fa)
{
    dfn[u]=low[u]=++cnt;
    s.push(u); vis[u]=1;
    for(int i=head[u]; i; i=e[i].next)
    {
        int v=e[i].to;
        if(v==fa) continue;
        if(!dfn[v])
        {
            tarjan(v,u);
            chk_min(low[u],low[v]);
        }
        else if(vis[v]) chk_min(low[u], low[v]);
    }

    if(dfn[u]==low[u])
    {
        clr[u]=++ccnt;
        while(s.top()!=u)
        {
            clr[s.top()]=ccnt;
            vis[s.top()]=0;
            s.pop();
        }
        s.pop(); vis[u]=0;
    }
}

bool can[MAXN];

int to,flag;
bool pas[MAXN];
void dfs(int u,bool f)
{
	if(flag)  return ;
    if(can[u]) f=1;
    if(u==to)
    {
        if(f) printf("YES\n");
        else printf("NO\n");
        flag=1;
    }
    pas[u]=1;
    for(int i=head[u]; i; i=e[i].next)
    {
        int v=e[i].to;
        if(!pas[v]) dfs(v,f|e[i].w);
    }
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		memset(e,0,sizeof(e));
		memset(head,0,sizeof(head));
		memset(low,0,sizeof(low));
		memset(dfn,0,sizeof(dfn));
		memset(vis,0,sizeof(vis));
		memset(clr,0,sizeof(clr));
		memset(can,0,sizeof(can));
		memset(pas,0,sizeof(pas));
		etot=cnt=ccnt=flag=0;
		int n,m;
	    scanf("%d%d",&n,&m);
	    for(int i=1; i<=m; ++i)
	    {
	        int u,v,w;
	        scanf("%d%d%d",&u,&v,&w);
	        add(u,v,w); add(v,u,w);
	    }
	    for(int i=1;i<=n;i++)  if(!dfn[i])  tarjan(i,0);
	    for(int i=1; i<=etot; i+=2)
	    {
	        if(clr[e[i].from] == clr[e[i].to] && e[i].w)
	        {
	            can[clr[e[i].from]]=1;
	        }
	    }
		memset(head,0,sizeof(head));
	    etot=0;
	    for(int i=1; i<=m*2; ++i)
	    {
	        if(clr[e[i].from] != clr[e[i].to])
	        {
	            add(clr[e[i].from],clr[e[i].to],e[i].w);
	        }
	    }
	    int start;
	    scanf("%d%d",&start,&to);
	    start=clr[start]; to=clr[to];
	    dfs(start,0);
	}
}

T4:情报传递

传送门

解析:先按照 1 1 中条件暴力连边,求出强连通分量,问题就变成了如何判断 2 2 中条件是否合法。

我们考虑直接dfs,那么时间复杂度在极端情况下会被卡成 O ( n 2 ) O(n^2) 的复杂度。考虑运用莫队的思想进行优化,我们在考虑同一个连通块时,我们只需要dfs一次,求出这个连通块可以到达的点,再将 2 2 中条件离线处理,排序后一次求解,因为数据较水,故该方法可以过。

#include<bits/stdc++.h>
using namespace std;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
int Read()
{
    int x = 0, f = 1;
    char c = gc();
    while (!isdigit(c))
    {
        if (c == '-')
            f = -1;
        c = gc();
    }
    while (isdigit(c))
    {
        x = (x << 3) + (x << 1) + (c ^ 48);
        c = gc();
    }
    return x * f;
}
int first[100005],nxt[100005],to[100005],tot=0;
map<pair<int,int>,int> mp;
void Add(int x,int y){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
}
int n,m,t,x[100005],y[100005];
int low[100005],dfn[100005],ind=0;
int col[100005],vis[100005],cnt=0;
stack<int> s;
void tarjan(int u){
	dfn[u]=low[u]=++ind;
	vis[u]=1;
	s.push(u);
	for(int e=first[u];e;e=nxt[e]){
		int v=to[e];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else  if(vis[v])  low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		++cnt;
		int q=s.top();
		do{
			q=s.top();
			s.pop();
			col[q]=cnt;
			vis[q]=0;
		}while(q!=u);
	}
}
struct que{
	int x,y,id;
}q[100005];
bool cmp(que p,que q){
	return (col[p.x]^col[q.x])?col[p.x]<col[q.x]:p.id<q.id;
}
int jl[100005],zj=0;
void dfs(int u){
	vis[u]=1;
	jl[++zj]=u;
	for(int e=first[u];e;e=nxt[e]){
		if(vis[to[e]])  continue;
		dfs(to[e]);
	}
}
int main(){
	n=Read();
	m=Read();
	for(int i=1;i<=m;i++){
		x[i]=Read(),y[i]=Read();
		Add(x[i],y[i]);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i])  tarjan(i);
	}
	memset(first,0,sizeof(first));
	memset(nxt,0,sizeof(nxt));
	memset(to,0,sizeof(to));
	tot=0;
	for(int i=1;i<=m;i++){
		if(col[x[i]]!=col[y[i]]){
			if(!mp[make_pair(col[x[i]],col[y[i]])]){
				Add(col[x[i]],col[y[i]]);
				mp[make_pair(col[x[i]],col[y[i]])]=1;
			}
		}
	}
	t=Read();
	for(int i=1;i<=t;i++){
		q[i].x=Read(),q[i].y=Read(),q[i].id=i;
	}
	int last=0;
	sort(q+1,q+t+1,cmp);
	for(int i=1;i<=t;i++){
		if(col[q[i].x]!=last){
			for(int j=1;j<=zj;j++){
				vis[jl[j]]=0;
				jl[j]=0;
			}
			zj=0;
			dfs(col[q[i].x]);
			last=col[q[i].x];
		}
		if(vis[col[q[i].y]]){
			cout<<"NO\n";
			return 0;
		}
	}
	cout<<"YES\n"<<m<<endl;
	for(int i=1;i<=m;i++){
		printf("%d %d\n",x[i],y[i]);
	}
}

正解是用一个 b i t s e t bitset 维护每两个点间是否连通,由于数据较大,达到了 1 0 5 10^5 ,所以我们考虑将数据分块,对于每块分别处理,时间复杂度为 O ( n K n K 64 ) O(\lceil\frac{n}{K}\rceil*n*\frac{K}{64}) 经检验,块大小为 20000 20000 时效率最高。

// 分块套 bitset 时间换空间  
#include<bits/stdc++.h>
#define cs const
using namespace std; 
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
cs int N = 1e5 + 5, K = 2e4;
int first[N], nxt[N], to[N], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
int n, m;
int low[N], dfn[N], sign, sta[N], top; bool insta[N];
int col[N], idx, du[N];
void dfs(int u){
	low[u] = dfn[u] = ++sign; sta[++top] = u; insta[u] = 1;
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(!dfn[t]) dfs(t), low[u] = min(low[u], low[t]);
		else if(insta[t] && dfn[t] < low[u]) low[u] = dfn[t];
	} if(dfn[u] == low[u]){ ++idx; do{col[sta[top]] = idx; insta[sta[top]] = 0;} while(sta[top--] != u);}
}
vector<int> v[N], v2[N];
bitset<N> val[K + 10];
int a[N], cnt, t;
int qx[N], qy[N];
void topsort(){
	queue<int> q;
	for(int i = 1; i <= idx; i++) if(!du[i]) q.push(i);
	while(!q.empty()){
		int x = q.front(); q.pop(); a[++cnt] = x;
		for(int i = 0; i < v[x].size(); i++){
			int t = v[x][i]; 
			if(--du[t] == 0) q.push(t);
		}
	}
}
bool ck(int l, int r){
	for(int i = 0; i <= K; i++) val[i].reset();
	for(int i = cnt; i >= 1; i--){
		int now = a[i]; 
		if(now < l || now > r) continue;
		val[now - l][now] = 1;
		for(int e = 0; e < v2[now].size(); e++){
			int t = v2[now][e]; 
			if(t >= l && t <= r) val[t - l] |= val[now - l];
		}
	}
	for(int i = 1; i <= t; i++){
		if(qx[i] >= l && qx[i] <= r && val[qx[i] - l][qy[i]]) return true;
	} return false;
}
int main(){
//	freopen("1.in","r",stdin);
	n = read(); m = read();
	for(int i = 1; i <= m; i++){
		int a = read(), b = read();
		add(a, b);
	}
	for(int i = 1; i <= n; i++) if(!dfn[i]) dfs(i);
	for(int i = 1; i <= n; i++){
		for(int e = first[i]; e; e = nxt[e]){
			int t = to[e]; if(col[t] ^ col[i]){
				v[col[i]].push_back(col[t]), ++du[col[t]];
				v2[col[t]].push_back(col[i]);
			}
		}
	}
	topsort();
	t = read();
	for(int i = 1; i <= t; i++){
		int x = read(), y = read();
		qx[i] = col[x], qy[i] = col[y];
	}
	for(int i = 1; i <= idx; i += K){
		int l = i, r = min(idx, i + K - 1);
		if(ck(l, r)){ puts("NO"); return 0;}
	}
	puts("YES");
	cout << m << '\n';
	for(int i = 1; i <= n; i++)
		for(int e = first[i]; e; e = nxt[e])
			cout << i << " " << to[e] << '\n';
	return 0;
}

猜你喜欢

转载自blog.csdn.net/wljoi/article/details/102556084
今日推荐