版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
主要知识点:差分约束系统,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狡猾的商人)
解析:思路很巧妙,将
转化为前缀和
,然后将
转成
然后用差分约束系统做即可。
#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:情报传递
解析:先按照 中条件暴力连边,求出强连通分量,问题就变成了如何判断 中条件是否合法。
我们考虑直接dfs,那么时间复杂度在极端情况下会被卡成 的复杂度。考虑运用莫队的思想进行优化,我们在考虑同一个连通块时,我们只需要dfs一次,求出这个连通块可以到达的点,再将 中条件离线处理,排序后一次求解,因为数据较水,故该方法可以过。
#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]);
}
}
正解是用一个 维护每两个点间是否连通,由于数据较大,达到了 ,所以我们考虑将数据分块,对于每块分别处理,时间复杂度为 经检验,块大小为 时效率最高。
// 分块套 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;
}