图论练习
我的图论太差,要多做些图论
loj526
(看了题解)
如果\((a_i,a_j)=1,(a_i+1,a_j+1)=1\),则\(i,j\)有无向边。
如果两个点之间有无向边,则不能同时选
就是最大独立集。
在补图中是最大团
这显然没有多项式级做法。
但是事实上图是二分图,所以可以用匹配做。
如果\(a_i,a_j\)奇偶性相同则一定有边。
所以在补图中,\(a_i,a_j\)如果奇偶性相同则一定没有边。
#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define int long long
int h[N],nxt[N],v[N],w[N],s,t,dep[N],ec,n,s1[N],s2[N],t1,t2,a[N];
void add(int a,int b,int c){v[++ec]=b;w[ec]=c;nxt[ec]=h[a];h[a]=ec;}
void adj(int a,int b,int c){
add(a,b,c);
add(b,a,0);
}
bool bfs(){
queue<int>q;
q.push(s);
for(int i=0;i<=t;i++)
dep[i]=0;
dep[s]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=h[x];i;i=nxt[i])
if(w[i]&&!dep[v[i]]){
dep[v[i]]=dep[x]+1;
q.push(v[i]);
if(v[i]==t)return 1;
}
}
return 0;
}
int dfs(int x,int dis){
if(x==t)
return dis;
int tp=dis;
for(int i=h[x];i;i=nxt[i])
if(dep[v[i]]==dep[x]+1&&w[i]){
int f=dfs(v[i],min(tp,w[i]));
if(!f)
dep[v[i]]=0;
tp-=f;
w[i]-=f;
w[i^1]+=f;
if(!tp)
break;
}
return dis-tp;
}
int din(){
int aans=0;
while(bfs()){
int v;
while(v=dfs(s,1e9))
aans+=v;
}
return aans;
}
int gcd(int a,int b){
return !b?a:gcd(b,a%b);
}
signed main(){
ec=1;
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
if(a[i]&1)
s1[++t1]=i;
else
s2[++t2]=i;
}
t=n+1;
for(int i=1;i<=t1;i++)
adj(s,s1[i],1);
for(int i=t1+1;i<=n;i++)
adj(s2[i-t1],t,1);
for(int i=1;i<=t1;i++)
for(int j=1;j<=t2;j++)
if(gcd(a[s1[i]],a[s2[j]])*gcd(a[s1[i]]+1,a[s2[j]]+1)==1)
adj(s1[i],s2[j],1);
printf("%lld\n",n-din());
}
lg3180(自己做出)
loj2077(自己做出)
考虑dag最小路径覆盖。
根据题意把一架飞机看作一个路径,一个航线看作一个点。
如果一架飞机在飞完一个航线后能够飞另一个航线,则这两条航线连一条边。
考虑求出在飞完一个航线后是否能够飞另一个航线。
假设现在时间为\(t\),则飞到下一个航线的代价是最短路径+维护时间。
考虑用floyd求最短路。
设\(d_{i,j}\)表示\(i\to j\)的最短时间。
求法是\(d_{i,j}\)初始值是\(i\)飞到\(j\)的时间加上\(j\)的维护时间。
然后floyd即可。
如果\(i\)航线和\(j\)航线有边,则判定\(d_{i,j}\)是否小于\(j\)的出发时间即可。
#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define int long long
int h[N],nxt[N],v[N],w[N],s,t,dep[N],ec,n,m,d[1010][1010],x[N],y[N],z[N],p[N],e[1010][1010];
void add(int a,int b,int c){v[++ec]=b;w[ec]=c;nxt[ec]=h[a];h[a]=ec;}
void adj(int a,int b,int c){
add(a,b,c);
add(b,a,0);
}
bool bfs(){
queue<int>q;
q.push(s);
for(int i=0;i<=t;i++)
dep[i]=0;
dep[s]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=h[x];i;i=nxt[i])
if(w[i]&&!dep[v[i]]){
dep[v[i]]=dep[x]+1;
q.push(v[i]);
if(v[i]==t)return 1;
}
}
return 0;
}
int dfs(int x,int dis){
if(x==t)
return dis;
int tp=dis;
for(int i=h[x];i;i=nxt[i])
if(dep[v[i]]==dep[x]+1&&w[i]){
int f=dfs(v[i],min(tp,w[i]));
if(!f)
dep[v[i]]=0;
tp-=f;
w[i]-=f;
w[i^1]+=f;
if(!tp)
break;
}
return dis-tp;
}
int din(){
int aans=0;
while(bfs()){
int v;
while(v=dfs(s,1e9))
aans+=v;
}
return aans;
}
signed main(){
ec=1;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&p[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
scanf("%lld",&d[i][j]);
e[i][j]=d[i][j];
}
for(int i=1;i<=m;i++)
scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)
d[i][j]+=p[j];
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
t=2*m+1;
for(int i=1;i<=m;i++)
adj(s,i,1);
for(int i=1;i<=m;i++)
adj(i+m,t,1);
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
if(i!=j&&z[i]+d[y[i]][x[j]]+p[y[i]]+e[x[i]][y[i]]<=z[j])
adj(i,j+m,1);
printf("%lld\n",m-din());
}
http://zijian-lv.com/contest/5/problem/39
(自己做出)
考虑差分约束
虽然题目的条件是\(l\leq x_i+y_j\leq r\)
但是转化后变成\(l-y_j\leq x_i\leq r-y_j\)
就是\(l+(-y_j)\leq x_i\leq r+(-y_j)\)
令\(z_i=-y_i\),则\(l+z_j\leq x_i\leq r+z_j\)
拆成两个限制:\(x_i\leq r+z_j\)和\(z_j\leq x_i-l\)
这样子就能够差分约束了。
题目要我们求\(\sum x_i-\sum y_i\)最大值,就是\(\sum x_i+\sum z_i\)最大值,用最短路即可。
每种基团的数量为非负整数,就是要求\(x_i\geq 0,z_i\leq 0\),从源点连边即可。
#include<bits/stdc++.h>
using namespace std;
#define N 500010
#define int long long
int d[N],v[N],nxt[N],h[N],ec,w[N],n,m,k,ans;
void add(int x,int y,int z){
v[++ec]=y;
w[ec]=z;
nxt[ec]=h[x];
h[x]=ec;
}
int bf(){
queue<int>q;
memset(d,127,sizeof(d));
d[0]=0;
q.push(0);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=h[x];i;i=nxt[i])
if(d[v[i]]>d[x]+w[i]){
d[v[i]]=d[x]+w[i];
q.push(v[i]);
}
}
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=k;i++){
int x,y,l,r;
scanf("%lld%lld%lld%lld",&x,&y,&l,&r);
add(y+n,x,r);
add(x,y+n,-l);
}
for(int i=1;i<=n;i++)
add(i,0,0);
for(int i=n+1;i<=n+m;i++)
add(0,i,0);
bf();
for(int i=1;i<=n+m;i++)
ans+=d[i];
printf("%lld",ans);
}
DGMATRIX
(看了题解)
省选原题。
我们如果知道了第一行第一列,我们就能知道整个矩阵。
设\(a_{1,1}=v\),\(a_{1,i+1}=x_i,a_{i+1,1}=y_i\)
如果\(v,b_{i,j}=0\)
可以发现,\(a_{i,j}=x_{i-1}*(-1)^{i-1}+y_{j-1}*(-1)^{j-1}\)
事实上,\(a_{i,j}=cx_{i-1}+dy_{i-1}\)
其中\(c,d\)取值为\(1,-1\)。
这事实上是\(l\leq cx_{i-1}+dy_{i-1}\leq r\)的约束
可以像前面一问一样设\(x_i,-x_i,y_i,-y_i\)四种变量解决。
但是有两个相反变量,它们的值必须是相反数,就是\(0\leq x_i+z_i\leq 0\)的限制条件。可以像上一问那样解决。
原问题中还有\(v,b_{i,j}\)。事实上\(b_{i,j}\)是常量,\(v\)可以枚举。
事实上就变成了对若干个变量\(0\leq x_i\leq 9,l\leq cx_i+dz_i\leq r\)的限制。也可以差分约束。
在省选那题中,由于数据范围变大所以不能枚举\(a_{1,1}\)
但是我们可以发现每个方格的值都形如\(v+cx_i+dy_j+ea\),\(v\)是常量。
如果把\(dy_j+ea\)视为整体,则也可以差分约束。
#include<bits/stdc++.h>
using namespace std;
#define lm 1000000
#define N 310
int h[N],w[N*N*3],v[N*N*3],nxt[N*N*3],ec,n,m,b[N][N],T,a[N][N],c[N*N],d[N*N],vi[N*N];
void add(int x,int y,int z){
v[++ec]=y;
w[ec]=z;
nxt[ec]=h[x];
h[x]=ec;
}
void adj(int a,int b,int c){
add(a,b,c);
add(b,a,lm-c);
}
queue<int>q;
int sp(){
q.push(1);
vi[1]=1;
memset(vi,0,sizeof(vi));
memset(d,127,sizeof(d));
memset(c,0,sizeof(c));
d[1]=0;
while(!q.empty()){
int x=q.front();
if(++c[x]>n+m)
return 1;
q.pop();
vi[x]=0;
for(int i=h[x];i;i=nxt[i])
if(d[v[i]]>d[x]+w[i]){
d[v[i]]=d[x]+w[i];
if(!vi[v[i]]){
vi[v[i]]=1;
q.push(v[i]);
}
}
}
return 0;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
memset(h,0,sizeof(h));
ec=0;
for(int i=2;i<=n;i++)
for(int j=2;j<=m;j++)
scanf("%d",&b[i][j]);
for(int i=2;i<=n;i++)
for(int j=2;j<=m;j++)
a[i][j]=b[i][j]-a[i][j-1]-a[i-1][j]-a[i-1][j-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if((i+j)&1)
adj(n+j,i,a[i][j]);
else
adj(i,n+j,a[i][j]);
}
int va=sp();
if(va){
puts("NO");
continue;
}
else{
puts("YES");
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int va=a[i][j];
if((i+j)&1)
va+=d[n+j]-d[i];
else
va+=d[i]-d[n+j];
printf("%d ",va);
}
puts("");
}
}
}
}
lg3530
(看了题解)
显然,判定无解可以直接差分约束,但是不能求出不同数个数的最大值。
把所有边加起来缩点后,显然1类边加的双向边会直接缩在一起。
剩下的都是2类边。
考虑找到每个强连通分量的最长路。
发现最长路+1就是这个连通块的答案。
发现最短路上有边两个节点的值的差都是小于1大于-1的。
而每个强连通分量忽略边的方向是连通的。
发现每个连通块的最长路+1的和就是总答案。
这是因为可以考虑按照逆拓扑序,第\(i\)位的值+=i*inf,然后在加上最短路值。
可以发现这样子能够构造出答案。
#include<bits/stdc++.h>
using namespace std;
#define N 400010
int n,m1,m2,nxt[N],cc,ec,id[N],bc[N],ct,lw[N],f[1010][1010],mx[N],v[N],h[N],a,b;
void add(int x,int y){
v[++ec]=y;
nxt[ec]=h[x];
h[x]=ec;
}
stack<int>s;
void dfs(int x){
id[x]=lw[x]=++cc;
s.push(x);
for(int i=h[x];i;i=nxt[i]){
if(!id[v[i]]){
dfs(v[i]);
lw[x]=min(lw[x],lw[v[i]]);
}
else if(!bc[v[i]])
lw[x]=min(lw[x],id[v[i]]);
}
if(lw[x]==id[x]){
ct++;
while(!s.empty()){
int y=s.top();
s.pop();
bc[y]=ct;
if(y==x)
break;
}
}
}
int main(){
scanf("%d%d%d",&n,&m1,&m2);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)
f[i][j]=1e9;
for(int i=1;i<=m1;i++){
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
f[a][b]=min(f[a][b],-1);
f[b][a]=min(f[b][a],1);
}
for(int i=1;i<=m2;i++){
scanf("%d%d",&a,&b);
add(a,b);
f[a][b]=min(f[a][b],0);
}
for(int i=1;i<=n;i++)
if(!id[i])
dfs(i);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(bc[k]==bc[i]&&bc[i]==bc[j]&&bc[j]==bc[k])
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(f[i][i]){
puts("NIE");
return 0;
}
int ans=ct;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(bc[i]==bc[j])
mx[bc[i]]=max(mx[bc[i]],f[i][j]);
for(int i=1;i<=ct;i++)
ans+=mx[i];
printf("%d",ans);
}
CF843D
(看了题解)
感觉并不是很难。。。。。
发现\(q\)和\(n\)相比不大。
考虑在\(O(n+m)\)的时间内解决一次询问。
虽然最短路可能很大,但是由于每次只+1,所以最短路的改变不是很大。
容易发现,在改变边权后,最短路最多只会\(+c\)。
设\(f_x\)表示节点\(x\)的增量,\(d\)表示最短路数组。
考虑用dij求出\(f\)。
每次取出增量最小的点,然后把\((x,y)\)边权重定为\(d_x+w-d_y\),然后更新其他点。
如果直接用堆维护会多一个log。
发现由于增量不大,考虑用桶+vector维护,就可以快速每次遍历距离\(=d\)的点。
显然,我们的数据结构内遍历的点权值随着时间的进行会不断上升。
所以\(d\)可以从小到大遍历。
时间复杂度\(O((m+n)\log_2n)+q(n+m))\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 200010
#pragma GCC optimize(2)
struct no{
int x,d;
};
int operator <(no x,no y){
return x.d>y.d;
}
int n,m,t,h[N],v[N],nxt[N],w[N],ec,d[N],f[N],vi[N];
vector<int>g[N];
priority_queue<no>q;
void dij(int s){
for(int i=1;i<=n;i++)
d[i]=1e18;
q.push((no){s,0});
d[s]=0;
memset(vi,0,sizeof(vi));
while(!q.empty()){
no x=q.top();
q.pop();
if(vi[x.x])
continue;
vi[x.x]=1;
for(int i=h[x.x];i;i=nxt[i])
if(d[v[i]]>d[x.x]+w[i]){
d[v[i]]=d[x.x]+w[i];
q.push((no){v[i],d[v[i]]});
}
}
}
void add(int x,int y,int z){
v[++ec]=y;
w[ec]=z;
nxt[ec]=h[x];
h[x]=ec;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&t);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
}
dij(1);
while(t--){
int op;
scanf("%lld",&op);
if(op==1){
int x;
scanf("%lld",&x);
if(d[x]<1e18)
printf("%lld\n",d[x]);
else
puts("-1");
}
else{
int c,x;
scanf("%lld",&c);
for(int i=1;i<=c;i++){
scanf("%lld",&x);
w[x]++;
}
for(int i=0;i<=c;i++)
g[i].clear();
for(int i=1;i<=n;i++){
f[i]=c+1;
vi[i]=0;
}
f[1]=0;
g[0].push_back(1);
for(int i=0;i<=c;i++){
for(int j=0;j<g[i].size();j++){
int x=g[i][j];
if(vi[x])
continue;
vi[x]=1;
f[x]=i;
for(int k=h[x];k;k=nxt[k]){
int nd=i+w[k]-d[v[k]]+d[x];
if(nd<=c){
g[nd].push_back(v[k]);
}
}
}
}
for(int i=1;i<=n;i++)
d[i]+=f[i];
}
}
}
lg6134
(看了题解)
感觉并不难。。。。。
删除一条边\(u\to v\),会对它入点反图的前驱能到达的点产生影响。
大力猜想:答案等于删除后原图连通性不变的边数。
如果删除这条边后\(u\)还要有到\(v\)的路径,则显然前驱能够到达的点不变。
考虑怎么求出合法的边数。
维护bitset \(F_i\)表示\(i\)能够到达的点。
按照逆拓扑序考虑每个点\(x\)的出边,设指向\(y\)。
把所有出边按照指向点拓扑序从小到大排序。
设考虑到出点为\(s\)的出边,对于所有出点拓扑序\(<v\)的出边,\(F_i|=F_v\)
一条路径只能从拓扑序小到大转移。
所以只需要判定\(F_{i,s}\)是否等于\(1\)即可。
时间复杂度\(O(\frac{nm}{w})\)
#include<bits/stdc++.h>
using namespace std;
#define N 200010
int n,d[N],m,h[N],v[N],ans,nxt[N],ec,st[N],tp,id[N],ss[N],tt;
void add(int x,int y){
v[++ec]=y;
nxt[ec]=h[x];
h[x]=ec;
}
bitset<30010>b[30010];
queue<int>q;
int cp(int x,int y){
return id[x]<id[y];
}
int main(){
scanf("%d%d",&n,&m);
while(m--){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
d[y]++;
}
for(int i=1;i<=n;i++)
b[i][i]=1;
for(int i=1;i<=n;i++)
if(!d[i])
q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
id[x]=++tp;
st[tp]=x;
for(int i=h[x];i;i=nxt[i]){
d[v[i]]--;
if(!d[v[i]])
q.push(v[i]);
}
}
for(int i=n;i;i--){
int x=st[i];
tt=0;
for(int j=h[x];j;j=nxt[j])
ss[++tt]=v[j];
sort(ss+1,ss+tt+1,cp);
for(int j=1;j<=tt;j++){
if(b[x][ss[j]])
ans++;
else
b[x]|=b[ss[j]];
}
}
printf("%d",ans);
}
「THUSCH 2017」巧克力
(看了题解)
当\(a_{i,j}\)比较小,显然可以使用斯坦纳树解决。
就是把所有被挤压过的点删除后跑斯坦纳树。
时间复杂度\(O(3^cs\log_2s)\)
当\(a_{i,j}\)较大时,由于\(k\)很小,考虑monte-carlo算法。
给每个点一个介于\(1\to k\)之间的随机权值。
然后跑斯坦纳树。
考虑正确率:如果最优方案被映射成各不相同的颜色,则合法。
为\(\frac{k!}{k^k}\)
随机200次以上即可保证基本正确。
第二问考虑经典的"01比较"技巧。
二分md,把>md的变成1,小于等于md的变成0。
只需要多定义一个\(g_{i,s}\)表示最小的\(1\)个数即可。
注意要先给权值 ,再二分。
被卡常的代码:
#include<bits/stdc++.h>
using namespace std;
#define N 240
int T,a[N][N],c[N][N],n,m,k,d[N][N],e[N][N],f[N],mx,v1,v2,tx[4]={1,0,-1,0},ty[4]={0,-1,0,1},vi[N][N];
struct no{
int x,y;
}v[N][N][1<<6];
queue<no>q;
int operator <(no x,no y){
return x.x<y.x||(x.x==y.x&&x.y<y.y);
}
no operator +(no x,no y){
return (no){x.x+y.x,x.y+y.y};
}
void bfs(int s){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
vi[i][j]=0;
if(c[i][j]!=-1){
q.push((no){i,j});
vi[i][j]=1;
}
}
while(!q.empty()){
no x=q.front();
q.pop();
vi[x.x][x.y]=0;
for(int i=0;i<4;i++){
int dx=tx[i]+x.x,dy=ty[i]+x.y;
if(dx<1||dx>n||dy<1||dy>m||c[dx][dy]==-1)
continue;
no va=v[x.x][x.y][s];
va.x++;
va.y+=d[dx][dy];
if(va<v[dx][dy][s]){
v[dx][dy][s]=va;
if(!vi[dx][dy]){
q.push((no){dx,dy});
vi[dx][dy]=1;
}
}
}
}
}
no run(){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int l=0;l<(1<<k);l++){
v[i][j][l].x=1e9;
v[i][j][l].y=0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(c[i][j]!=-1){
v[i][j][1<<e[i][j]].x=1;
v[i][j][1<<e[i][j]].y=d[i][j];
}
for(int i=1;i<(1<<k);i++){
for(int j=1;j<=n;j++)
for(int l=1;l<=m;l++)
if(c[j][l]!=-1){
for(int s=i;s;s=(s-1)&i){
no va=v[j][l][i-s]+v[j][l][s];
va.x--;
va.y-=d[j][l];
v[j][l][i]=min(v[j][l][i],va);
}
}
bfs(i);
}
no ans;
ans.x=1e9;
ans.y=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(c[i][j]!=-1)
ans=min(ans,v[i][j][(1<<k)-1]);
return ans;
}
int main(){
//srand(time(NULL));
scanf("%d",&T);
while(T--){
if(T==0){
T++;
T--;
}
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
scanf("%d%d%d",&n,&m,&k);
mx=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&c[i][j]);
mx=max(mx,c[i][j]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
v1=v2=1e9;
for(int s=1;s<=240;s++){
int l=0,r=1000000,aa=1e6+5;
no va=(no){1000000000,0};
for(int i=1;i<=mx;i++){
f[i]=rand()%k;
}
while(l<=r){
int md=(l+r)/2;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(c[i][j]!=-1){
if(a[i][j]<=md)
d[i][j]=0;
else
d[i][j]=1;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(c[i][j]!=-1)
e[i][j]=f[c[i][j]];
no ans=run();
va=min(va,ans);
if(ans.x-ans.y<(ans.x+1)/2)
l=md+1;
else{
r=md-1;
aa=md;
}
}
if(va.x<v1&&va.x<1e8){
v1=va.x;
v2=aa;
}
else if(va.x==v1&&aa<v2){
v2=aa;
}
}
if(v1<1e8)
printf("%d %d\n",v1,v2);
else
puts("-1 -1");
}
}
jzoj4858
(自己做出)
考虑fmt优化建图。
考虑枚举每一种值。
\(x\)向\(x-bt\)(\(bt\)为\(x\)的某一位)连\(0\)。
把所有相同权值的节点挂在同一个点上。
然后对所有相同权值新建一个点\(y\)。
相同权值点\(z\)向\(y\)连\(0\),\(y\)向\(z\)连\(1\),\(z\)向fmt对应位连\(0\)。
发现空间开不下。
可以不建立\(x\)向\(x-bt\)的边。
然后用01bfs即可。
代码用的spfa。
#include<bits/stdc++.h>
using namespace std;
#define N 2100010
queue<int>q;
int n,m,ct,w[N],v[N],nxt[N],h[N],ec,d[N],va[200010],mx,lg;
vector<int>g[N];
void add(int x,int y,int z){
v[++ec]=y;
w[ec]=z;
nxt[ec]=h[x];
h[x]=ec;
}
int main(){
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&va[i]);
g[va[i]].push_back(i);
mx=max(mx,va[i]);
}
while((1<<lg)<=mx)
lg++;
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x+(1<<lg),y+(1<<lg),1);
}
ct=m;
for(int i=0;i<(1<<lg);i++){
for(int j=0;j<g[i].size();j++){
add(g[i][j]+(1<<lg),i,1);
add(i,g[i][j]+(1<<lg),0);
}
}
memset(d,63,sizeof(d));
d[1+(1<<lg)]=0;
q.push(1+(1<<lg));
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=h[x];i;i=nxt[i])
if(d[v[i]]>d[x]+w[i]){
d[v[i]]=d[x]+w[i];
q.push(v[i]);
}
if(x<(1<<lg)){
for(int i=0;i<20;i++)
if((x&(1<<i))){
int t=x-(1<<i);
if(d[t]>d[x]){
d[t]=d[x];
q.push(t);
}
}
}
}
for(int i=1;i<=n;i++){
if(d[i+(1<<lg)]>1e9){
puts("-1");
}
else{
printf("%d\n",d[i+(1<<lg)]);
}
}
}
lg3264
(看了题解)
不知道为什么简单题还要看题解。
考虑最后的每个连通块。
连通块肯定包含了某些颜色的全部点。
一个连通块的代价就是把它连通起来的最小边权。
可以用斯坦纳树求。
考虑对连通块进行dp。
设\(f_s\)表示连通\(s\)节点的代价,\(g_s\)表示\(s\)节点的答案。
则\(g_s=\min(f_{s-t},g_t)\)。
时间复杂度\(O(3^p+2^pm\log_2n)\)
#include<bits/stdc++.h>
using namespace std;
#define N 200010
int n,m,p,v[N],nxt[N],h[N],w[N],f[3000][3010],*dis,vis[N],r[N],mn[N],ec,c[N],d[N],st[N],vi[1010],ss[1010];
void add(int x,int y,int z){v[++ec]=y;w[ec]=z;nxt[ec]=h[x];h[x]=ec;}
struct no{
int x,i;
}a[N];
int operator <(no x,no y){
return x.x<y.x;
}
queue<int>q;
int pd(int x){
if(!x)
return 1;
for(int i=1;i<=n;i++)
vi[i]=0;
int va=0,ok=1,ct=0;
for(int i=0;i<p;i++)
if(x&(1<<i)){
if(!vi[a[i].x])
st[++ct]=a[i].x;
vi[a[i].x]++;
}
for(int i=1;i<=ct;i++)
if(vi[st[i]]!=ss[st[i]])
return 0;
return 1;
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
memset(f,63,sizeof(f));
for(int i=0;i<p;i++){
scanf("%d%d",&a[i].x,&a[i].i);
}
int ct=0;
sort(a,a+p);
for(int i=0;i<p;i++){
if(!vi[a[i].x]){
vi[a[i].x]=++ct;
a[i].x=ct;
}
else
a[i].x=vi[a[i].x];
}
for(int i=0;i<p;i++)
ss[a[i].x]++;
for(int i=0;i<p;i++)
f[1<<i][a[i].i]=0;
memset(r,127,sizeof(r));
for(int s=1;s<(1<<p);s++){
for(int i=1;i<=n;i++)
for(int j=s;j;j=(j-1)&s)
if(f[j][i]<1e9)
for(int k=h[i];k;k=nxt[k])
f[s][i]=min(f[s][i],f[j][i]+w[k]+f[s-j][v[k]]);
dis=f[s];
for(int i=1;i<=n;i++)
q.push(i),vis[i]=1;
while(!q.empty()){
int x=q.front();q.pop();
vis[x]=0;
for(int i=h[x];i;i=nxt[i])
if(dis[v[i]]>dis[x]+w[i]){
dis[v[i]]=dis[x]+w[i];
if(!vis[v[i]]){
vis[v[i]]=1;
q.push(v[i]);
}
}
}
mn[s]=2e9;
for(int i=1;i<=n;i++)
mn[s]=min(mn[s],dis[i]);
if(pd(s))
r[s]=mn[s];
}
r[0]=0;
for(int s=1;s<(1<<p);s++)
for(int i=s;i;i=s&(i-1))
if(i!=s&&pd(s-i)&&pd(i))
r[s]=min(r[s-i]+mn[i],r[s]);
if(r[(1<<p)-1]<1e9)
printf("%d",r[(1<<p)-1]);
else printf("-1");
}
mst的几条重要性质:
1.mst操作有"结合律",就是可以先把一些子集边做mst,排除没有选的边,然后把剩下边做mst.
2.mst边的权值集合是相同的。
3.非树边的权值必须比它路径上所有树边的权值大。
4.k氏法过程得到的是最小边字典序最小生成树。
APIO2013 toll
(看了题解)
由于\(k\)很小,考虑枚举每条边是否使用,然后决定权值。
钦定被选的边必须在mst中,把这些边缩成一个点。
然后根据kruskal法,从小到大加边。
根据mst的性质,非树边的权值必须比它路径上所有树边的权值大。
由于边的权值互不相同,mst唯一。
如果加的边的两个端点不连通,则这条边必须在mst上。
否则路径上的所有边的权值都要小于插入的边。
可以暴力取min。
然而时间复杂度为\(O(2^kmn)\),每次要做mst,根本无法通过。
注意到\(n,m\)和\(k\)相比巨大。
很多边都是必须在这\(2^k\)个mst中的。
可以把这\(k\)条边插入后把其他边求mst以求出交集。
理解是:如果我们插入了这\(k\)条边的子集,则连通块数只会更多,保留的原图的边只会更多。
求出必定在mst的边后,现在只有最多\(O(k^2)\)条边了
(重边只需要保留权值最小的)
然后运行上面的暴力算法即可。
时间复杂度\(O(n\log_2n+2^kk^3)\)
#include<bits/stdc++.h>
using namespace std;
#define N 500010
#define int long long
int n,m,k,p[N],id[N],ii[N],f[N],st[N],tp,ss[N],ts,ans,mn[30][30],ct,w[N],h[N],v[N],nxt[N],ec,va[N],ff[N],d[N],vv[N],fg[N],sz[N];
void add(int x,int y,int z){
v[++ec]=y;
ii[ec]=z;
nxt[ec]=h[x];
h[x]=ec;
}
struct no{
int x,y,z;
}a[N],b[N],c[N];
int operator <(no x,no y){
return x.z<y.z;
}
int fd(int x){
return !f[x]?x:f[x]=fd(f[x]);
}
void dfs(int x,int fa){
d[x]=d[fa]+1;
sz[x]=w[x];
for(int i=h[x];i;i=nxt[i])
if(v[i]!=fa){
ff[v[i]]=ii[i];
fg[v[i]]=x;
dfs(v[i],x);
sz[x]+=sz[v[i]];
}
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=m;i++)
scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].z);
sort(a+1,a+m+1);
for(int i=0;i<k;i++){
scanf("%lld%lld",&b[i].x,&b[i].y);
int xx=fd(b[i].x),yy=fd(b[i].y);
if(xx!=yy){
f[xx]=yy;
}
}
for(int i=1;i<=n;i++)
scanf("%lld",&p[i]);
for(int i=1;i<=m;i++){
int xx=fd(a[i].x),yy=fd(a[i].y);
if(xx!=yy){
f[xx]=yy;
st[++tp]=i;
}
else{
ss[++ts]=i;
}
}
memset(f,0,sizeof(f));
for(int i=1;i<=tp;i++){
int xx=fd(a[st[i]].x),yy=fd(a[st[i]].y);
if(xx!=yy){
f[xx]=yy;
}
}
for(int i=1;i<=n;i++){
if(fd(i)==i){
id[i]=++ct;
w[ct]=p[i];
}
}
for(int i=1;i<=n;i++)
if(fd(i)!=i){
id[i]=id[fd(i)];
w[id[i]]+=p[i];
}
memset(mn,127,sizeof(mn));
for(int i=1;i<=ts;i++)
mn[id[a[ss[i]].x]][id[a[ss[i]].y]]=min(mn[id[a[ss[i]].x]][id[a[ss[i]].y]],a[ss[i]].z);
ts=0;
for(int j=1;j<=ct;j++)
for(int l=1;l<=ct;l++)
if(j!=l&&mn[j][l]<1e17)
c[++ts]=(no){j,l,mn[j][l]};
sort(c+1,c+ts+1);
for(int i=1;i<(1<<k);i++){
ec=0;
for(int j=0;j<k*k+5;j++)
h[j]=f[j]=ff[j]=vv[j]=sz[j]=d[j]=fg[j]=0;
for(int j=0;j<k+5;j++)
va[j]=1e13;
for(int j=0;j<k;j++)
if(i&(1<<j)){
int c=id[b[j].x],d=id[b[j].y];
int xx=fd(c),yy=fd(d);
if(xx!=yy){
f[xx]=yy;
add(c,d,j);
add(d,c,j);
}
else
va[j]=0;
}
for(int j=1;j<=ts;j++){
int xx=fd(c[j].x),yy=fd(c[j].y);
if(xx!=yy){
f[xx]=yy;
add(c[j].x,c[j].y,-1);
add(c[j].y,c[j].x,-1);
}
else
vv[j]=1;
}
dfs(id[1],0);
for(int j=1;j<=ts;j++)
if(vv[j]){
int x=c[j].x,y=c[j].y;
if(d[x]>d[y])
swap(x,y);
while(d[x]!=d[y]){
if(ff[y]!=-1)
va[ff[y]]=min(va[ff[y]],c[j].z);
y=fg[y];
}
while(x!=y){
if(ff[x]!=-1)
va[ff[x]]=min(va[ff[x]],c[j].z);
if(ff[y]!=-1)
va[ff[y]]=min(va[ff[y]],c[j].z);
x=fg[x];
y=fg[y];
}
}
int vv=0;
for(int j=0;j<k;j++)
if(i&(1<<j)){
int x=id[b[j].x],y=id[b[j].y];
if(d[x]>d[y])
swap(x,y);
vv+=sz[y]*va[j];
}
ans=max(ans,vv);
}
printf("%lld",ans);
}
uoj176
(看了题解)
去除重复,可以把所有值为\(x\)的点合并在一起,每合并一个,ans+=x
做法1:考虑boruvka算法。
每个连通块要找到它的最小出边。
这事实上等于询问一个点和一个点集的最小and和。
可以类似最小xor和这样做。
但是如果and的某一位位为\(0\)的话,可以向\(0/1\)走,十分麻烦。
考虑trie树合并。
把每个点的\(1\)子树复制一遍合并到\(0\)子树上,这样子就能保证正确了。
做法2:考虑kruskal算法。
从大到小枚举边权\(i\)。
考虑fmt,枚举另一位\(j\),使得\(j\)和\(i\)没有交。
考虑在fmt的过程中合并它的所有\(i+j\)。
发现\(i+j\)的超集事实上已经被合并在一起了。
我们把\(i\)和\(i+j\)的连通块合并在以前即可。
在实现上,我们要找到任意一个\(j\)使得\(i+j\)存在连通块才能合并。
合并操作可以给每个\(i\)一个代表元\(id_i\),就可以快速合并连通块。
#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define int long long
int n,m,mx,id[N],p[N],a[N],ans,v;
int fd(int x){
return !p[x]?x:p[x]=fd(p[x]);
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
if(id[a[i]])
ans+=a[i];
else
id[a[i]]=i;
mx=max(mx,a[i]);
}
v=1;
while(v<mx)
v*=2;
for(int i=v-1;~i;i--){
for(int j=1;j<v;j*=2)
if(!(i&j)&&!id[i])
id[i]=id[i|j];
for(int j=1;j<v;j*=2){
if(!(i&j)&&id[i]&&id[i|j]){
int xx=fd(id[i]),yy=fd(id[i|j]);
if(xx==yy)
continue;
else{
p[xx]=yy;
ans+=i;
}
}
}
}
printf("%lld",ans);
}
lg6256
(看了题解)
不知道为什么这种题还要看题解。。。
考虑离散化,把每条直线的\(x\)值离散化,显然答案不变。
考虑dp,设\(f_{i,j}\)表示从下往上第\(i\)个雨棚,要落到第\(j\)的位置的答案。
假设模板是左低右高的。
枚举打洞的位置,可得:\(f_{i, j} = \min \left\{ f_{i - 1, l - 1}, 1 + \min_{l \leq k \leq j} f_{i - 1, k} \quad \left( l \leq i \leq r \right) \right\}\)
\(f_{i - 1, l - 1}\)表示没有打洞。
考虑快速维护\(f\)。
事实上,问题等于:对于\(l...r\)的所有点,让\(f_i+1\),然后\(f_i=\min(f_i,f_{i-1})\)。
这可以用线段树维护:这等于差分表上单点修改。
\(f_i=\min(f_i,f_{i-1})\)这事实上可以找到下面一个\(>0\)的位置。
容易分析出均摊时间复杂度\(O(n\log_2n)\)。
接下来问题就是:要求一个序,使得下面的雨棚在上面的雨棚后面。
这事实上是"记忆中的水杉树"问题。
如果我们暴力连边:如果\(x\)在\(y\)上面,则\(x\to y\)连边,则时间复杂度太高。
考虑扫描线优化连边。
把所有线段拆成两个事件,从左往右扫,维护大小关系。
发现由于线段不相交,所以每个线段在扫描线中大小关系都是确定的。
当插入一条线段时,理论上需要给set中所有线段连边。
然而事实上发现之前所有被插入过的直线的关系已经确定,所以只需要给set的前驱和后继连边即可。
删除显然不用管。
时间复杂度\(O(n\log_2n)\)。
生成树计数(看了题解)
连通性dp,用最小表示法。
从左向右给每个连通块按照其编号最小的点排序后标号。
事实上证明,在\(k=5\),状态数量只有\(52\)个。
在转移时,暴力枚举前面和当前点的连通情况。
用并查集判定有没有环。
后面的测试点中,\(n\)十分大。
但是我们可以预处理出状态的转移系数。
这样子就可以矩阵乘法了。
lg4926
(自己做出)
又是裸题。
考虑二分T。
问题转化成:求出是否存在s使得\(s_a>s_b*(k_i-T),s_a>\frac{s_b}{k_i+T}\)
考虑使用经典的ln技巧。
原式变成\(\ln(s_a)>\ln(s_b)+\ln(k_i-T),\ln(s_a)>\ln(s_b)-\ln(k_i+T)\)
这事实上就可以差分约束了。
有等于可以拆成两个限制。
#include<bits/stdc++.h>
using namespace std;
#define lm 1000000
#define N 300010
int h[N],v[N],nxt[N],ec,n,m,t,vi[N],c[N],o[N],a[N],b[N],k[N],va[N];
double d[N],w[N],ans=-1,l=0,r=1e9;
void add(int x,int y,double z){
v[++ec]=y;
w[ec]=z;
nxt[ec]=h[x];
h[x]=ec;
}
queue<int>q;
int sp(){
while(!q.empty())
q.pop();
q.push(0);
vi[0]=1;
memset(vi,0,sizeof(vi));
for(int i=1;i<=n;i++)
d[i]=-1e9;
memset(c,0,sizeof(c));
d[0]=0;
while(!q.empty()){
int x=q.front();
if(++c[x]>n+1)
return 1;
q.pop();
vi[x]=0;
for(int i=h[x];i;i=nxt[i])
if(d[v[i]]<d[x]+w[i]){
d[v[i]]=d[x]+w[i];
if(!vi[v[i]]){
vi[v[i]]=1;
q.push(v[i]);
}
}
}
return 0;
}
int pd(double x){
memset(h,0,sizeof(h));
ec=0;
for(int i=1;i<=n;i++)
if(va[i]){
add(0,i,log(va[i]));
add(i,0,-log(va[i]));
}
for(int i=1;i<=m;i++){
if(o[i]==1)
add(b[i],a[i],log(k[i]-x));
if(o[i]==2)
add(b[i],a[i],-log(k[i]+x));
}
return !sp();
}
int main(){
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&o[i],&a[i],&b[i],&k[i]);
if(o[i]==1)
r=min(r,(double)k[i]);
}
for(int i=1;i<=t;i++){
int c,x;
scanf("%d%d",&c,&x);
va[c]=x;
}
if(pd(0)){
puts("-1");
return 0;
}
while(r-l>1e-9){
double md=(l+r)*0.5;
if(pd(md))
ans=r=md;
else
l=md;
}
printf("%.9lf",ans);
}
CF1221G
(自己做出)
套路题。
考虑容斥,容斥成没有限制-只有01-只有12-只有02+只有0+只有1+只有2
没有限制答案就是\(2^{n}\)
如果只有\(0\),则每个连通块只能选\(0\),但是孤立点可以任意选,答案是\(2^{孤立点数}\)
只有\(2\)同理可得答案是\(2^{孤立点数}\)
只有\(1\)代表每条边连接节点两端权值不同。
如果有连通块不是二分图,则答案为\(0\),否则答案是\(2^{连通块数}\)。
只有\(12\)和只有\(01\)是对称的。
只有\(02\)说明每个连通块的值都是相同的,答案是\(2^{连通块数}\)。
只有\(01\)事实上要求独立集个数。
取反就成了团数,可以用前面提到的做法解决。
#include<bits/stdc++.h>
using namespace std;
#define N 70
#define M 20
#define int long long
int h[N],v[N*N],nxt[N*N],ec,ans,n,m,a[N][N],d[N],cl[N],c,f[1<<(M+2)],b[N],va,l,r,bt[N],lg[1<<(M+3)],g[1<<(M+2)],b2[1<<(M+2)],ou[N],b1[1<<(M+2)];
void add(int x,int y){
v[++ec]=y;
nxt[ec]=h[x];
h[x]=ec;
}
void fwt(int *f){
for(int i=2,l=1;i<=(1<<r);i*=2,l*=2)
for(int j=0;j<(1<<r);j+=i)
for(int k=0;k<l;k++)
f[j+k+l]+=f[j+k];
}
void d1(int x){
for(int i=h[x];i;i=nxt[i]){
if(cl[v[i]]==-1){
cl[v[i]]=cl[x]^1;
d1(v[i]);
}
else if(cl[v[i]]==cl[x]){
c=-1;
return;
}
}
}
void d2(int x){
if(cl[x])
return;
cl[x]=1;
for(int i=h[x];i;i=nxt[i])
if(!cl[v[i]])
d2(v[i]);
}
signed main(){
scanf("%lld%lld",&n,&m);
if(!m){
printf("0");
return 0;
}
bt[0]=1;
for(int i=1;i<60;i++)
bt[i]=bt[i-1]*2;
for(int i=0;i<21;i++)
lg[(1<<i)]=i;
for(int i=1;i<=m;i++){
int x,y;
scanf("%lld%lld",&x,&y);
x--;
y--;
a[x][y]=a[y][x]=1;
add(x,y);
add(y,x);
d[x]++;
d[y]++;
}
for(int i=0;i<n;i++)
cl[i]=-1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i!=j)
a[i][j]=!a[i][j];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
if(a[i][j])
ou[i]|=(1ll<<j);
ou[i]|=(1ll<<i);
}
int ans=1ll<<n;
for(int i=0;i<n;i++)
if(!d[i])
c++;
ans+=(1ll<<(c+1));
c=0;
for(int i=0;i<n;i++)
if(cl[i]==-1){
cl[i]=0;
d1(i);
if(c!=-1)
c++;
}
if(c!=-1)
ans+=(1ll<<c);
c=0;
memset(cl,0,sizeof(cl));
for(int i=0;i<n;i++)
if(!cl[i]){
d2(i);
c++;
}
ans-=(1ll<<c);
l=n/2,r=n-l;
for(int i=0;i<n;i++){
b[i]=ou[i]|(1ll<<i);
b[i]&=((1ll<<l)-1);
}
for(int i=0;i<bt[l];i++){
if(i)g[i]=g[i-(i&-i)]&b[lg[i&-i]];
else g[i]=(1ll<<l)-1;
if((g[i]&i)==i){
va++;
b1[i]=1;
}
}
va--;
for(int i=0;i<n;i++){
b[i]=ou[i]|(1ll<<i);
b[i]>>=l;
}
memset(g,0,sizeof(g));
for(int i=0;i<bt[r];i++){
if(i)g[i]=g[i-(i&-i)]&b[lg[i&-i]+l];
else g[i]=(1ll<<r)-1;
if((g[i]&i)==i){
va++;
if(i)b2[i]=1;
}
}
fwt(b2);
memset(g,0,sizeof(g));
for(int i=0;i<bt[l];i++){
if(i)g[i]=g[i-(i&-i)]&b[lg[i&-i]];
else g[i]=(1ll<<r)-1;
if(g[i]&&i&&b1[i])
va+=b2[g[i]];
}
ans-=va*2;
printf("%lld",ans);
}
图函数
(瞄了一眼题解)
设\(G(i,j,k)\)表示删除前\(i\)条边,\(1\to j-1\)编号的点后,\(j\)和\(k\)是否强联通。
显然,对于所有\(j,k\),存在一个分界线\(l\),使得\(G(1...l,j,k)\)为\(1\),\(G(l+1...m,j,k)\)为\(0\)。
接下来考虑怎么求分界线。
考虑怎么求出单个分界线。
如果\(k\)合法,这要求删除\(1...j-1\)后,\(j,k\)存在一条路径的所有边都\(\geq k\)。
这说明,我们要让\(j...k\)路径最小值最大。
可以用dij求出。
事实上边权很小,所以可以用桶维护dij。
时间复杂度\(O(n(n+m))\)
#include<bits/stdc++.h>
using namespace std;
#define N 1010
#define int long long
int a[N][N],n,m,ans[1000010];
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
a[i][i]=m+1;
for(int i=1;i<=m;i++){
int x,y;
scanf("%lld%lld",&x,&y);
a[x][y]=i;
}
for(int k=n;k;k--){
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
a[i][j]=max(a[i][j],min(a[i][k],a[k][j]));
for(int i=k+1;i<=n;i++)
for(int j=1;j<k;j++)
a[i][j]=max(a[i][j],min(a[i][k],a[k][j]));
}
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
ans[min(a[i][j],a[j][i])]++;
for(int i=m;i;i--)
ans[i]+=ans[i+1];
for(int i=1;i<=m+1;i++)
printf("%lld ",ans[i]);
}
巨神兵
(看了题解)
以前做过类似的,但是由于不擅长组合数学和图论所以不会做。
一个图合法的条件:它能够拓扑排序。
一个图显然存在集合使得入度点为\(0\)。
考虑枚举入度为\(0\)的点。
但是发现这样子会多计算它的超集。
考虑容斥,\(f_s+=f_{s-t}*[边全部从t\to s-t]*(-1)^{card(t)-1}\)
考虑它的正确性。
一个入点集合为\(S\)方案会在它的子集内被计算。
发现\(\sum_{i=1}^n(-1)^{i-1}{n\choose i}=[n>0]\)
恰好符合要求。
被卡常数的代码:
#include<bits/stdc++.h>
using namespace std;
#define N 20
#define int long long
#define mo 1000000007
int f[1<<N],rc[1<<N],g[1<<N],x[N*N],y[N*N],n,m;
int bc(int x){
int r=0;
while(x){
r+=x&1;
x>>=1;
}
return r;
}
int qp(int x,int y){
int r=1;
for(;y;y>>=1,x=x*x%mo)
if(y&1)
r=r*x%mo;
return r;
}
signed main(){
freopen("obelisk.in","r",stdin);
freopen("obelisk.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
scanf("%lld%lld",&x[i],&y[i]);
x[i]--;
y[i]--;
}
for(int i=0;i<(1<<n);i++)
rc[i]=qp(mo-1,bc(i)+1);
f[0]=1;
for(int i=1;i<(1<<n);i++){
for(int j=i;j;j=(j-1)&i){
int va=1;
for(int k=1;k<=m;k++)
if(((1<<x[k])&j)&&((1<<y[k])&(i-j)))
va=va*2%mo;
f[i]=(f[i]+f[i-j]*va%mo*rc[j]%mo)%mo;
}
}
printf("%lld\n",f[(1<<n)-1]);
}
sdoi2014重建
(自己做出)
简单题。
考虑枚举一个生成树,计算贡献。
树边的贡献是\(p_{i,j}\)
非树边的贡献是\(1-p_{i,j}\)
然而这不能直接用矩阵树定理计算:矩阵树定理的树边的贡献是权值,非树边是\(1\)。
考虑让非树边的贡献变成1:把所有树边的贡献变成\(\frac{p_{i,j}}{1-p_{i,j}}\),答案再乘以\(\prod (1-p_{i,j})\)
正确性显然
#include<bits/stdc++.h>
using namespace std;
#define N 60
double a[N][N],w[N][N],ans=1;
int n;
double gt(){
double va=1;
for(int i=1;i<n;i++){
if(!a[i][i]){
for(int j=i+1;j<n;j++)
if(a[j][i]){
va=-va;
for(int k=1;k<n;k++)
swap(a[i][k],a[j][k]);
break;
}
if(fabs(a[i][i])<1e-7)
return 0;
}
double v=a[i][i];
va*=v;
for(int j=i;j<n;j++)
a[i][j]=a[i][j]/v;
for(int j=i+1;j<n;j++)
for(int k=n-1;k>=i;k--)
a[j][k]=a[j][k]-a[j][i]*a[i][k];
}
for(int i=1;i<n;i++)
va=va*a[i][i];
return va;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
scanf("%lf",&w[i][j]);
if(w[i][j]>1-1e-7)
w[i][j]-=1e-7;
if(i<j)
ans*=(1-w[i][j]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j){
double v=w[i][j]/(1-w[i][j]);
a[i][i]+=v;
a[i][j]-=v;
}
printf("%.12lf\n",gt()*ans);
}
CF1035G
(看了题解)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 1000010
int c[N],n,ans,v=1,mx,p[N];
int fd(int x){
return p[x]==x?x:p[x]=fd(p[x]);
}
void mg(int x,int y,int w){
x=fd(x);
y=fd(y);
if(x!=y){
ans+=(c[x]+c[y]-1)*w;
p[x]=y;
c[y]=1;
}
}
signed main(){
c[0]++;
scanf("%lld",&n);
for(int i=0;i<n;i++){
int x;
scanf("%lld",&x);
c[x]++;
mx=max(mx,x);
ans-=x;
}
while(v<mx)
v*=2;
for(int i=0;i<v;i++)
p[i]=i;
for(int i=v-1;~i;i--){
for(int j=i;j;j=(j-1)&i)
if(c[j]&&c[i^j])
mg(j,i^j,i);
if(c[i]&&c[0])
mg(i,0,i);
}
printf("%lld",ans);
}
lg4426
(自己做出)
简单题。
以前做过但是忘了
如果是树,则显然是个简单的树dp就能解决的。
是图可以构造一颗生成树,由于边数很少,可以容斥。
容斥就是钦定一些边不符合要求,就是钦定一些点必须选。
用动态dp维护。
Painting Edges
(自己做出)
简单题。
如果有"执行后合法"这个限制,显然可以用时间轴线段树解决:
维护\(k\)个并查集,一条边的染色事实上就是一种颜色的边消失,另一种颜色的边出现。
每种边的每种颜色会对应线段树上的一个区间,插入到线段树上dfs解决。
但是现在有后效性了。
考虑按照时间顺序遍历叶子节点(就是先dfs左儿子,再dfs右儿子,线段树都是这么写的),它代表一个询问。
发现,时间轴线段树上的某一个编号是\(i\)的边对应的区间,可能性比较少:
把所有编号为\(i\)的边的询问拿出来,按照时间顺序排序为\(a_1,a_2...a_n\)
则在时刻\([1,a_1),[a_1,a_2)...[a_{n-1},a_n)\)的边的颜色都是相同的。
考虑在遍历到\(a_i\)时把区间\([a_i,a_{i+1})\)(对应的存在区间)染成对应颜色,然后插到线段树里。
在遍历到\(a_i\)时,判定把\(a_i\)插入染后的新图是否合法。
如果合法,则插入区间\((a_i,a_{i+1})\)。
时间复杂度\(O(n\log_2^2n)\)
随机二分图
(看了题解)
想了几分钟就想到了\(t=0\),然而接下来没有仔细想。。。。。
如果\(t=0\),发现左边点按照小到大确定匹配点,不会算重。
设\(f_{i,s}\)表示左边\([1...i-1]\)都匹配,右边匹配\(s\)集合的期望。
可以直接转移。
但是现在\(t=0,1,2\)。
考虑把\(1,2\)拆成两条边后跑\(0\)。
然而事实上这是有错的。
如果\(t=1\),则当两条边\((u,v)(x,y)\)同时出现,概率是\(\frac{1}{2}\),然而这里计算的是\(\frac{1}{4}\)。
考虑新建一个转移\((u,v)(x,y)\)表示能够同时选择左边\((u,v)\)右边\((x,y)\),出现概率是\(\frac{1}{4}\),这样子就正确了。
\(t=2\)同理,新建一个转移\((u,v)(x,y)\)表示能够同时选择左边\((u,v)\)右边\((x,y)\),出现概率是\(-\frac{1}{4}\)
考虑dp求解,设\(g_{s1,s2}\)表示左边未匹配\(s1\),右边未匹配\(s2\)的方案数。
不能像前面设置状态,这是因为\(s1\)不一定是一段前缀。
但是像\(t=1\)一样,可以每次枚举\(s1\)最小位,钦定一定要选择这一位转移。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mo 1000000007
#define N 1000
int x[N],y[N],n,m,ct,w[N];
map<int,int>ma;
int qp(int x,int y){
int r=1;
for(;y;y>>=1,x=x*x%mo)
if(y&1)
r=r*x%mo;
return r;
}
int dfs(int v){
if(ma.count(v))
return ma[v];
if(v==((1<<(2*n))-1))
return 1;
int va=0,lb=0;
for(int i=0;i<n;i++)
if(!(v&(1<<i))){
lb=(1<<i);
break;
}
for(int i=1;i<=ct;i++)
if(((x[i]&lb)==lb)&&((x[i]&v)==0))
va=(va+w[i]*dfs(v|x[i]))%mo;
return ma[v]=va;
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
int t;
scanf("%lld",&t);
if(t==0){
ct++;
scanf("%lld%lld",&x[ct],&y[ct]);
x[ct]--;
y[ct]--;
x[ct]=(1<<x[ct])|(1<<(y[ct]+n));
w[ct]=qp(2,mo-2);
}
if(t==1){
ct++;
scanf("%lld%lld",&x[ct],&y[ct]);
x[ct]--;
y[ct]--;
x[ct]=(1ll<<x[ct])|(1ll<<(y[ct]+n));
w[ct]=qp(2,mo-2);
ct++;
scanf("%lld%lld",&x[ct],&y[ct]);
x[ct]--;
y[ct]--;
x[ct]=(1ll<<x[ct])|(1ll<<(y[ct]+n));
w[ct]=qp(2,mo-2);
if((x[ct-1]&x[ct])==0){
ct++;
x[ct]=x[ct-2]|x[ct-1];
w[ct]=qp(4,mo-2);
}
}
if(t==2){
ct++;
scanf("%lld%lld",&x[ct],&y[ct]);
x[ct]--;
y[ct]--;
x[ct]=(1ll<<x[ct])|(1ll<<(y[ct]+n));
w[ct]=qp(2,mo-2);
ct++;
scanf("%lld%lld",&x[ct],&y[ct]);
x[ct]--;
y[ct]--;
x[ct]=(1ll<<x[ct])|(1ll<<(y[ct]+n));
w[ct]=qp(2,mo-2);
if((x[ct-1]&x[ct])==0){
ct++;
x[ct]=x[ct-2]|x[ct-1];
w[ct]=(mo-qp(4,mo-2))%mo;
}
}
}
printf("%lld",dfs(0)*qp(2,n)%mo);
}
CF1519F
(看了题解)
本来想到流,但是后来完全走偏了,往hall定理想了。。。。。
如果知道上锁的集合,可以用最大权闭合子图求解:
所有箱子的权值是\(a_i\),如果箱子\(i\)需要锁\(j\)才能打开,则\(j\)的权值是\(-b_j\),\(a_i\)向\(b_j\)连边。
有解是二分图右边点满流。
对于原问题考虑状压,一个一个的插入二分图左边的点,我们要知道左边的点喷出的流量。
由于中间的边权值是\(\inf\),所以不用管,只需要管右边点的权值。
设\(f_{i,s}\)表示左边插入到\([1...i]\),右边每条边还有\(s\)流量的最小代价,\(s\)用五进制压缩。
转移可以枚举\(i\)喷出的流量集合,代价事实上就是\(i\)连出的有流量的边的集合的权值。
这个dp事实上可以用背包加速,但是没有必要,暴力枚举\(i\)给右边每条边喷出多少流量即可。
事实上证明此题暴力搜索也能过,出的比较失败
#include<bits/stdc++.h>
using namespace std;
#define N 10
#define M 1000010
int n,m,f[N][M],a[N],b[N],c[N][N],tw[N];
vector<int>g;
void dfs(int x,int p,int fl,int cv,vector<int>g){
if(x==m){
if(fl)
return;
int s=0;
for(int i=0;i<m;i++)
s+=tw[i]*g[i];
f[p+1][s]=min(f[p+1][s],cv);
return;
}
for(int i=0;i<=fl;i++)
if(g[x]>=i){
g[x]-=i;
if(i)
dfs(x+1,p,fl-i,cv+c[p+1][x+1],g);
else
dfs(x+1,p,fl-i,cv,g);
g[x]+=i;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&c[i][j]);
tw[0]=1;
for(int i=1;i<N;i++)
tw[i]=tw[i-1]*5;
memset(f,127,sizeof(f));
int s=0;
for(int i=0;i<=m;i++)
s+=tw[i-1]*b[i];
f[0][s]=0;
for(int i=0;i<n;i++)
for(int j=0;j<tw[m];j++)
if(f[i][j]<=1e9){
g.clear();
int v=1;
for(int k=0;k<m;k++){
v*=5;
g.push_back((j%v)/(v/5));
}
dfs(0,i,a[i+1],f[i][j],g);
}
int ans=1e9;
for(int i=0;i<tw[m];i++)
ans=min(ans,f[n][i]);
if(ans==1e9)
puts("-1");
else
printf("%d\n",ans);
}
lg6765
(瞄了一眼题解)
考虑二分,两个点合法的条件:
1.两个点连通(显然)
2.两个点所在连通块不是一条链(画图可得)
考试时连这都没看出。。。。
事实上,可以用整体二分,然而有强制在线。
考虑kruskal重构树,但是要改一下,每个节点可能有多个儿子
类似kruskal重构树从小到大加边,维护"合法关系连通块",两个在同一个连通块的点对可能不在同一个"合法关系连通块"上,但是两个不在同一个合法关系连通块上的点一定不连通。
在合并连通块时,维护这个连通块是否合法,以及这个连通块的点集,用启发式合并维护。
判定一个连通块是否是一条链,可以维护连通块链的两个端点。
当当前合并的边的两端点连通,如果当前连通块有标记,什么也不用做。
否则标记当前连通块,并且把所有连通块的点连向一个新建的权值为当前合并边的点。
当两端点不连通,合并的两个连通块至少有一个有标记,则把没有标记的连通块的所有点连向一个新建的权值为当前合并边的点。(可能什么也不用做)
否则,当合并的边是两边链的两个端点,则这个连通块还是不合法,什么也不用做。
否则这个连通块变合法,新建一个权值为边权的点,把这个连通块的所有点连到这个点上。
#include<bits/stdc++.h>
using namespace std;
#define ss 500010
struct no{
int x,y,z;
}a[ss];
int operator<(no x,no y){
return x.z<y.z;
}
int d[ss],f[ss],tp[ss],sz[ss],p[ss],h[ss],v[ss],nxt[ss],ec,ff[ss],bz[ss],l[ss],r[ss],ct,w[ss],st[10],tt,n,m,q;
vector<int>g[ss];
void add(int x,int y){
v[++ec]=y;
nxt[ec]=h[x];
h[x]=ec;
}
int fd(int x){
return !ff[x]?x:ff[x]=fd(ff[x]);
}
void d1(int x,int fa){
sz[x]=1;
f[x]=fa;
for(int i=h[x];i;i=nxt[i]){
d[v[i]]=d[x]+1;
d1(v[i],x);
sz[x]+=sz[v[i]];
if(sz[v[i]]>sz[p[x]])
p[x]=v[i];
}
}
void d2(int x,int fa,int tt){
tp[x]=tt;
if(p[x])
d2(p[x],x,tt);
for(int i=h[x];i;i=nxt[i])
if(v[i]!=p[x])
d2(v[i],x,v[i]);
}
int lca(int x,int y){
while(tp[x]!=tp[y]){
if(d[tp[x]]>d[tp[y]])
x=f[tp[x]];
else
y=f[tp[y]];
}
if(d[x]>d[y])
return y;
return x;
}
void init(int N, int M, std::vector<int> U, std::vector<int> V, std::vector<int> W){
n=N;
m=M;
for(int i=0;i<m;i++){
a[i+1].x=U[i];
a[i+1].y=V[i];
a[i+1].z=W[i];
a[i+1].x++;
a[i+1].y++;
}
sort(a+1,a+m+1);
for(int i=1;i<=n;i++){
g[i].push_back(i);
l[i]=r[i]=i;
}
ct=n;
for(int i=1;i<=m;i++){
int x=a[i].x,y=a[i].y;
int xx=fd(x),yy=fd(y);
if(g[xx].size()>g[yy].size())
swap(xx,yy);
if(xx==yy){
if(bz[xx])
continue;
else{
ct++;
bz[xx]=ct;
w[ct]=a[i].z;
for(int j=0;j<g[xx].size();j++)
add(ct,g[xx][j]);
}
}
else{
ff[xx]=yy;
if(bz[xx]||bz[yy]){
ct++;
w[ct]=a[i].z;
if(bz[xx]){
add(ct,bz[xx]);
}
else{
for(int j=0;j<g[xx].size();j++)
add(ct,g[xx][j]);
}
if(bz[yy]){
add(ct,bz[yy]);
}
else{
for(int j=0;j<g[yy].size();j++)
add(ct,g[yy][j]);
}
bz[yy]=ct;
}
else{
tt=0;
st[++tt]=l[xx];
st[++tt]=r[xx];
st[++tt]=l[yy];
st[++tt]=r[yy];
int cc=0,p1=0,p2=0,v1=0,v2=0;
for(int j=1;j<=tt;j++){
if(!v1&&x==st[j]){
cc++;
v1=1;
}
else if(!v2&&y==st[j]){
cc++;
v2=1;
}
else if(!p1)
p1=st[j];
else
p2=st[j];
}
if(cc==2){
l[yy]=p1;
r[yy]=p2;
}
else{
ct++;
w[ct]=a[i].z;
bz[yy]=ct;
for(int j=0;j<g[xx].size();j++)
add(ct,g[xx][j]);
for(int j=0;j<g[yy].size();j++)
add(ct,g[yy][j]);
}
}
for(int j=0;j<g[xx].size();j++)
g[yy].push_back(g[xx][j]);
}
}
d1(ct,0);
d2(ct,0,ct);
}
int getMinimumFuelCapacity(int X, int Y){
int x=X,y=Y;
x++;
y++;
int va=w[lca(x,y)];
if(!va)
return -1;
return va;
}
时间复杂度\(O(m\log_2n)\)
支配
(看了题解)
#include<bits/stdc++.h>
using namespace std;
#define N 6010
int n,m,q,vi[N],h[N],v[N*2],nxt[N*2],ec,f[N],id[N],ct,st[N],va[N],a[N][N];
void add(int x,int y){
v[++ec]=y;
nxt[ec]=h[x];
h[x]=ec;
}
vector<int>dm[N],g[N],ig[N];
void bfs(int p){
queue<int>q;
q.push(1);
memset(vi,0,sizeof(vi));
while(!q.empty()){
int x=q.front();
q.pop();
if(vi[x]||x==p)
continue;
vi[x]=1;
for(int i=0;i<g[x].size();i++){
int v=g[x][i];
if(!vi[v]&&v!=p){
q.push(v);
}
}
}
for(int i=1;i<=n;i++)
if(!vi[i])
dm[i].push_back(p);
}
void dfs(int x){
id[x]=++ct;
st[ct]=x;
for(int i=h[x];i;i=nxt[i])
dfs(v[i]);
}
void bb(int f,int x){
queue<int>q;
q.push(1);
memset(vi,0,sizeof(vi));
while(!q.empty()){
int x=q.front();
q.pop();
if(vi[x]||x==f)
continue;
vi[x]=1;
for(int i=0;i<g[x].size();i++){
int v=g[x][i];
if(!vi[v]&&v!=f){
q.push(v);
}
}
}
for(int i=1;i<=n;i++)
if(vi[i])
a[id[x]][i]=1;
q.push(x);
memset(vi,0,sizeof(vi));
while(!q.empty()){
int x=q.front();
q.pop();
if(vi[x]||x==f)
continue;
vi[x]=1;
for(int i=0;i<ig[x].size();i++){
int v=ig[x][i];
if(!vi[v]&&v!=f){
q.push(v);
}
}
}
for(int i=1;i<=n;i++)
if(vi[i])
a[id[x]][i]=2;
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
ig[y].push_back(x);
}
for(int i=1;i<=n;i++)
bfs(i);
for(int i=1;i<=n;i++){
for(int j=0;j<dm[i].size();j++){
int x=dm[i][j];
if(dm[x].size()+1==dm[i].size())
f[i]=x;
}
}
for(int i=2;i<=n;i++)
add(f[i],i);
dfs(1);
for(int i=2;i<=n;i++)
bb(f[i],i);
while(q--){
memset(va,0,sizeof(va));
int x,y,ans=0;
scanf("%d%d",&x,&y);
for(int i=1;i<=n;i++){
int p=st[i];
va[p]=va[p]|va[f[p]];
if(a[i][x]==1&&a[i][y]==2)
va[p]=1;
ans+=va[p];
}
printf("%d\n",ans);
}
}
radius
(瞄了一眼题解)
枚举一条边\(x,y\),枚举它的中点位置,设到\(x\)是\(d-w\),到\(y\)是\(w\)
计算答案的方法:把\(x,y\)裂成\((x,z,d-w)\),\((y,z,w),w\in[0,d]\)这两条边,然后求出\(x\)到所有点的距离的最小值的最大值。
用floyd求出两两之间的最短路,设为\(d_{i,j}\),答案等于\(\max(\min(d_{i,x}+d-w,d_{j,y}+w))\)
考虑经典的拆\(\min\),显然\(\min(d_{i,x}+d-w,d_{j,y}+w)\)内当\(w\)比较大(\(>\)一个确定的值\(v\))时,这个式子取左边,否则取右边。
随着\(v\)的增大,一些取右边的式子会变成取左边。
把所有\(v\)排序,把\(\max\)内元素视为直线,则问题变成有插入删除的半平面交问题。
由于斜率只有\(2\)类,所以可以把同一斜率分为一类,用堆维护其中的最大值。
事实上发现斜率为\(-1\)的集合只会删除,\(+1\)的只会插入元素,\(+1\)集合的最大值显然能\(O(1)\)维护,\(-1\)的最大值可以倒过来后\(O(1)\)维护。
时间复杂度\(O(n(n^2+m\log_2n))\)
不知道为什么调了这么久
#include<bits/stdc++.h>
using namespace std;
#define N 210
struct no{
int x,y,id;
}e[N*N];
int n,m,d[N][N],a[N*N],b[N*N],c[N*N],tp;
double ans=1e18,m1[N*N],m2[N*N];
int operator <(no x,no y){
return x.x-x.y<y.x-y.y;
}
int main(){
freopen("radius.in","r",stdin);
freopen("radius.out","w",stdout);
scanf("%d%d",&n,&m);
memset(d,63,sizeof(d));
for(int i=1;i<=n;i++)
d[i][i]=0;
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[i]=x;
b[i]=y;
c[i]=z;
d[x][y]=min(d[x][y],z);
d[y][x]=min(d[y][x],z);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
for(int i=1;i<=m;i++){
int x=a[i],y=b[i],z=c[i];
tp=0;
for(int j=1;j<=n;j++)
e[++tp]=(no){d[x][j]+z,d[y][j],j};
sort(e+1,e+tp+1);
for(int j=0;j<=tp+2;j++)
m1[j]=m2[j]=-1e18;
for(int j=1;j<=tp;j++)
m1[j]=max(m1[j-1],1.0*e[j].x);
for(int j=tp;j;j--)
m2[j-1]=max(m2[j],1.0*e[j].y);
m1[0]=1e18;
m2[tp+1]=-1e18;
double va=1e18;
for(int j=0;j<=tp;j++){
double l=(e[j].x-e[j].y)*0.5,r=(e[j+1].x-e[j+1].y)*0.5;
l=max(l,0.0);
r=min(r,1.0*z);
double md=(m1[j]-m2[j])*0.5;
if(l<=md&&md<=r)
va=min(va,m1[j]-md);
else{
if(j!=tp)
va=min(va,max(m1[j]-r,r+m2[j]));
if(j)
va=min(va,max(m1[j]-l,l+m2[j]));
}
}
ans=min(ans,va);
}
printf("%.2lf",ans);
}

浙公网安备 33010602011771号