10.25
图论:欧拉回路,缩点, 2-SAT
一些 NOI Online
UVA12118 检查员的难题
题意:
\(N\) 个点的无向完全图,可以重复经过边,求经过指定的边最少要经过多少条边。 \((1\le N\le1000)\)
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int tot,n,m,k,ans;
bool flag,vis[N];
vector<int> e[N];
int dfs(int x){
if(vis[x]) return 0;vis[x]=true;int res=e[x].size()%2;
for(int i=0;i<e[x].size();i++) res+=dfs(e[x][i]);return res;
}
void solve(){
ans=m;flag=false;fill_n(vis+1,n,false);
for(int i=1;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(int i=1;i<=n;i++)
if(!vis[i]&&!e[i].empty()) {ans+=max((dfs(i)-2)/2,0);if(flag) ans++;flag=true;}
printf("Case %d: %d\n",++tot,ans*k);
}
signed main(){
while(~scanf("%d%d%d",&n,&m,&k)&&(n||m||k)) solve();
return 0;
}
POJ2186 受欢迎的牛
题意:
给定一个 \(N\) 个点 \(M\) 条边的有向图,求有多少个点是所有点都可以到达的。 \((1\le N\le10^4,1\le M\le5\times10^4)\)
题解:
首先一个强连通分量里的点是不会影响结果的,先缩点。
接着构成了一个有向无环图,当有多个出度为 \(0\) 的点时则无解。否则答案就是那个出度为 \(0\) 的点包含的点数。
LA4287 等价性证明
题意:
求最少加几条边使得一张有向图强联通。 \((1\le N\le2\times10^4,1\le M\le5\times10^5)\)
题解:
首先将图给缩点,也考虑这个有向无环图。
只需要给没有入度的点和没有出度的点连边,发现只需要以数量少的为基准交叉连成一个环,剩下的多的随便连一下,则答案为这两种点中个数更多的。
如果两种都符合,就都加,仍然是正确的。注意特判只有一个点的情况。
UVA11324 最大团
题意:
求有向图最长的一条有向路径(不一定简单)。 \((1\le N\le10^3,1\le M\le5\times10^4)\)
代码:
发现一种写法:因为 tarjan
出来的编号倒序就是一个合法的拓扑序,所以在有向无环图上就不需要再拓扑排序一遍,直接从后往前扫。
#include <bits/stdc++.h>
#define y e[x][i]
#define to g[i][j]
using namespace std;
const int N=1005;
int t,n,m,tp,s1,s2,ans,s[N],dfn[N],low[N],bel[N],sum[N],f[N];
vector<int> e[N],g[N];
void tarjan(int x){
int&l=low[x];dfn[x]=l=++s1;s[++tp]=x;
for(int i=0;i<e[x].size();i++)
if(!dfn[y]) {tarjan(y);l=min(l,low[y]);}
else if(!bel[y]) l=min(l,dfn[y]);
if(dfn[x]!=l) return;s2++;
for(int i=0;i!=x;tp--,sum[s2]++) bel[i=s[tp]]=s2;
}
void solve(){
scanf("%d%d",&n,&m);tp=s1=s2=ans=0;
for(int i=1;i<=n;i++) {e[i].clear();g[i].clear();dfn[i]=low[i]=bel[i]=sum[i]=f[i]=0;}
while(m--) {int u,v;scanf("%d%d",&u,&v);e[u].push_back(v);}
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]) g[bel[x]].push_back(bel[y]);
for(int i=s2;i;i--)
for(int j=0;j<g[i].size();j++) f[to]=max(f[to],f[i]+sum[i]);
for(int i=1;i<=s2;i++) ans=max(ans,f[i]+sum[i]);
printf("%d\n",ans);
}
signed main(){
scanf("%d",&t);while(t--) solve();
return 0;
}
LOJ10092 最大半连通子图
题意:
求有向图最长的一条有向路径(不一定简单)并输出方案数模 \(X\) 。 \((1\le N\le10^5,1\le M\le10^6,1\le X\le10^8)\)
题解:
求最长参考上面,方案数的时候如果能把最长的更新了,就直接转移过来,否则加上去即可。
LA3713 Astronauts
题意:
给出 \(N\) 小于 \(200\) 的数字,大于等于平均数的分到 A 或 C 组,小于的分到 B 或 C 组,有 \(M\) 个限制表示第 \(x\) 和第 \(y\) 个数不能分在一起,构造方案或判断无解。 \((1\le N,M\le10^5)\)
代码:
#include <bits/stdc++.h>
#define y e[x][i]
using namespace std;
const int N=2e5+5;
int t,n,m,k,tp,s1,s2,sum,a[N],s[N],dfn[N],low[N],bel[N];
vector<int> e[N];
void tarjan(int x){
int&l=low[x];dfn[x]=l=++s1;s[++tp]=x;
for(int i=0;i<e[x].size();i++)
if(!dfn[y]) {tarjan(y);l=min(l,low[y]);}
else if(!bel[y]) l=min(l,dfn[y]);
if(dfn[x]!=l) return;s2++;
for(int i=0;i!=x;tp--) bel[i=s[tp]]=s2;
}
void solve(){
k=n*2;tp=s1=s2=sum=0;
for(int i=1;i<=k;i++) {e[i].clear();dfn[i]=low[i]=bel[i]=0;}
for(int i=1;i<=n;i++) {scanf("%d",&a[i]);sum+=a[i];}
for(int i=1;i<=n;i++) a[i]=a[i]*n>=sum;
while(m--){
int u,v;scanf("%d%d",&u,&v);
e[u+n].push_back(v);e[v+n].push_back(u);
if(a[u]==a[v]) {e[u].push_back(v+n);e[v].push_back(u+n);}
}
for(int i=1;i<=k;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
if(bel[i]==bel[i+n]) {puts("No solution.");return;}
for(int i=1;i<=n;i++) puts(bel[i]<bel[i+n]?a[i]?"A":"B":"C");
}
signed main(){
while(~scanf("%d%d",&n,&m)&&n&&m) solve();
return 0;
}
NOI Online #1 提高组 序列
题意:
给出长为 \(N\) 序列 \(A\) 和 \(B\) ,有 \(Q\) 个可以使用无限次的操作:
-
1 u v
:让 \(A_u,A_v\) 同时加 \(1\) 或减 \(1\) 。 -
2 u v
:让 \(A_u\) 加 \(1\) ,\(A_v\) 减 \(1\) ;或 \(A_u\) 减 \(1\) ,\(A_v\) 加 \(1\) 。
问序列 \(A\) 能否变成 \(B\) 。 \((1\le N,Q\le 10^5)\)
代码
#include <bits/stdc++.h>
#define y e[x][i]
using namespace std;
typedef long long ll;
const int N=1e5+5;
int t,n,m,s,a[N],b[N],p[N],q[N],f[N],c[N];
ll ss[3],val[N];
vector<int> e[N];
int getf(int x) {return f[x]==x?x:f[x]=getf(f[x]);}
bool dfs(int x,int k){
c[x]=k;ss[k]+=val[x];bool res=true;
for(int i=0;i<e[x].size();i++){
if(!c[y]&&!dfs(y,3-k)) res=false;
if(c[y]==k) res=false;
}
return res;
}
bool solve(){
scanf("%d%d",&n,&m);s=0;
for(int i=1;i<=n;i++) {scanf("%d",&a[i]);f[i]=i;val[i]=c[i]=0;e[i].clear();}
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
while(m--){
int opt,u,v;scanf("%d%d%d",&opt,&u,&v);
if(opt==1) {p[++s]=u;q[s]=v;}
else f[getf(u)]=getf(v);
}
for(int i=1;i<=n;i++) val[getf(i)]+=b[i]-a[i];
for(int i=1;i<=s;i++) {int u=getf(p[i]),v=getf(q[i]);e[u].push_back(v);e[v].push_back(u);}
for(int i=1;i<=n;i++)
if(getf(i)==i&&!c[i]){
ss[1]=ss[2]=0;bool flag=dfs(i,1);
if(flag&&ss[1]!=ss[2]) return false;
if(!flag&&(ss[1]+ss[2])%2) return false;
}
return true;
}
signed main(){
scanf("%d",&t);while(t--) puts(solve()?"YES":"NO");
return 0;
}
NOI Online #1 提高组 最小环
题意:
在一个长度为 \(N\) 的环中,长度为 \(N\) 的序列 \(A\) 。给出 \(M\) 个 \(K\) ,对于每一对距离为 \(K\) 的数对,有两数乘积的贡献。(这个数对有两次要都算),求出这 \(M\) 个答案。 \((1\le M\le N\le2\times 10^5,0\le K\le\frac{N}{2})\)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m;
ll a[N],ans[N];
int gcd(int x,int y) {return y?gcd(y,x%y):x;}
ll solve(int k){
if(!k){
ll r=0;
for(int i=1;i<=n;i++) r+=a[i]*a[i];
return r;
}
int c=n/gcd(n,k);ll&s=ans[c];if(s) return s;
for(int i=0;i<n;i+=c){
s+=a[i+1]*a[i+2]+a[i+c-1]*a[i+c];
for(int j=1;j<=c-2;j++) s+=a[i+j]*a[i+j+2];
}
return s;
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
sort(a+1,a+n+1,greater<ll>());
while(m--) {int x;scanf("%d",&x);printf("%lld\n",solve(x));}
return 0;
}
NOI Online #2 提高组 涂色游戏
题意:
给格子染色, \(p_1\) 倍数的染一种, \(p_2\) 倍数的染另一种,都是则两种都可以,没被染色的直接删除,求是否能让最长连续的一段小于等于 \(k\) 。
\(T\) 组数据。 \((1\le T\le10^6,1\le p_1,p_2,k\le10^9)\)
代码:
#include <bits/stdc++.h>
using namespace std;
int t,a,b,k;
int gcd(int x,int y) {return y?gcd(y,x%y):x;}
void solve(){
scanf("%d%d%d",&a,&b,&k);
if(k==1) {puts("NO");return;}
if(b>a) swap(a,b);
puts(((a-gcd(a,b)-1)/b+1)>=k?"NO":"YES");
}
signed main(){
scanf("%d",&t);while(t--) solve();
return 0;
}
NOI Online #2 提高组 子序列问题
题意:
给定长度为 \(N\) 序列 \(A\) 。
令 \(f(l,r)\) 为从 \(l\) 到 \(r\) 不同的值得个数。
求 \(\sum_{l=1}^{n}\sum_{r=l}^{n}f(l,r)^2 \mod 10^9+7\)
\((1\le N\le10^6,1\le A_i\le 10^9)\)
代码:
#include <bits/stdc++.h>
#define ii inline
#define lc pos<<1
#define rc pos<<1|1
using namespace std;
const int N=1e6+5,mod=1e9+7;
int n,m,ans,a[N],b[N],lst[N],pre[N],t1[N<<2],t2[N<<2],tag[N<<2];
ii void pushup(int pos) {t1[pos]=(t1[lc]+t1[rc])%mod;t2[pos]=(t2[lc]+t2[rc])%mod;}
ii void add(int pos,int l,int r,int x) {tag[pos]+=x;(t2[pos]+=1ll*(r-l+1)*x*x%mod)%=mod;(t2[pos]+=2ll*x*t1[pos]%mod)%=mod;(t1[pos]+=1ll*(r-l+1)*x%mod)%=mod;}
ii void pushdown(int pos,int l,int r,int mid) {if(tag[pos]) {add(lc,l,mid,tag[pos]);add(rc,mid+1,r,tag[pos]);tag[pos]=0;}}
void update(int l,int r,int pos,int L,int R){
if(L<=l&&r<=R) {add(pos,l,r,1);return;}int mid=(l+r)>>1;pushdown(pos,l,r,mid);
if(L<=mid) update(l,mid,lc,L,R);if(R>mid) update(mid+1,r,rc,L,R);pushup(pos);
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) {scanf("%d",&a[i]);b[i]=a[i];}
sort(b+1,b+n+1);m=unique(b+1,b+n+1)-b;
for(int i=1;i<=n;i++) {a[i]=lower_bound(b+1,b+m,a[i])-b;pre[i]=lst[a[i]];lst[a[i]]=i;}
for(int i=1;i<=n;i++) {update(1,n,1,pre[i]+1,i);(ans+=t2[1])%=mod;}
printf("%d\n",ans);
return 0;
}
另:为啥某些图片在电脑上看是好的在钉钉上是花的下载下来又清除了