题意:共对n个元素进行m次操作,操作共有两种,$M\ a\ b$ 为定义 $a$ 与 $b$ 为同一类元素,$S\ a$ 定义为将 $a$ 节点与其他节点分离。输出共有几类元素。
操作1很好处理,关键在于操作2,考虑到在并查集中不好操作,于是想到如下方法:将删除的元素映射为一个新的元素 $mp[x]$,并且后续所有涉及该元素的操作都用 $mp[x]$ 进行操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> using namespace std;int n,m,lst;int fa[2000005 ];int mp[100005 ];int getfa (int x) { return fa[x] = fa[x]==x?x:getfa (fa[x]); } void merge (int x,int y) { fa[getfa (y)]=getfa (x); } void solve () { int mp[n+1 ]; for (int i=0 ;i<n;i++)mp[i]=i; lst=n; for (int i=0 ;i<=n+m+1 ;i++)fa[i]=i; for (int i=1 ;i<=m;i++) { char c; cin>>c; int a,b; if (c=='M' ) { cin>>a>>b; merge (mp[a],mp[b]); } if (c=='S' ) { cin>>a; mp[a]=lst++; } } int ans=0 ; int check[n+m+1 ]={0 }; for (int i=0 ;i<n;i++) { int fa=getfa (mp[i]); if (check[fa])continue ; check[fa]=1 ; ans++; } cout<<ans<<endl; } int main () { int t=0 ; while (cin>>n>>m && n || m) { cout<<"Case #" <<++t<<": " ; solve (); } return 0 ; }
题意:有 $n$ 个点 $m$ 条双向边,可以挑 $k$ 条边使其权值变为0,求从1号点到 $n$ 号点的最短距离。
Dijkstra的变形,在距离数组 $d$ 中多加一维 $d[i][ud]$,表示在使用了 $ud$ 条免费边后点 $i$ 到1号点的最短距离。在更新距离时也按照两种情况来更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <bits/stdc++.h> using namespace std;#define int long long int n,m,k;priority_queue<pair<int ,pair<int ,int > > > q; int d[100050 ][35 ];bool vis[100050 ][35 ];int hed[100050 ],to[100050 ],nxt[100050 ],edg[100050 ],cnt;void add (int x,int y,int w) { to[++cnt]=y; edg[cnt]=w; nxt[cnt]=hed[x]; hed[x]=cnt; } signed main () { cin>>n>>m>>k; for (int i=1 ;i<=m;i++) { int x,y,w; cin>>x>>y>>w; add (x,y,w); add (y,x,w); } memset (d,0x3f ,sizeof (d)); for (int j=0 ;j<=30 ;j++)d[1 ][j]=0 ; q.push ({0 ,{1 ,0 }}); while (q.size ()) { int u=q.top ().second.first; int ud=q.top ().second.second; q.pop (); if (vis[u][ud])continue ; vis[u][ud]=1 ; for (int i=hed[u];i;i=nxt[i]) { int v=to[i]; int w=edg[i]; if (!vis[v][ud] && d[v][ud]>d[u][ud]+w) { d[v][ud]=d[u][ud]+w; q.push ({-d[v][ud],{v,ud}}); } if (ud<k && !vis[v][ud+1 ] && d[v][ud+1 ]>d[u][ud]) { d[v][ud+1 ]=d[u][ud]; q.push ({-d[v][ud+1 ],{v,ud+1 }}); } } } int ans=0x3f3f3f3f3f3f3f3f ; for (int i=0 ;i<=k;i++)ans=min (ans,d[n][i]); cout<<ans; return 0 ; }
题意:给一幅带权图,有 $m$ 个点,$e$ 条双向边,以及天数 $n$ ,每一天都要从1号点走到 $n$ 号点。但是每一天都会有一些点不可通过,而且如果当天与前一天的路线不同,当天的消耗会在距离基础上额外加上一个 $k$ 值。求 $n$ 天后的最小总花费。$1\leq n\leq 100, 1\leq m\leq 20,1\leq e\leq50$ 。
根据数据范围考虑暴力枚举求出第 $i$ 天到第 $j$ 天每天都用同一条道的消耗 $co[i][j]$,然后设计 $dp[i]$ 表示截止第 $i$ 天的总消耗,那么有:
$$
dp[i]=min(dp[j]+co[j+1][i]\times(i-j)+k)
$$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <bits/stdc++.h> using namespace std;#define LL long long int n,m,k,e,d;int hed[1000 ],nxt[1000 ],to[1000 ],w[1000 ],cnt;int stuck[30 ][250 ],co[250 ][250 ];int vis[30 ];int dis[30 ];LL dp[250 ]; void add (int x,int y,int z) { to[++cnt]=y; w[cnt]=z; nxt[cnt]=hed[x]; hed[x]=cnt; } void dij () { memset (dis,0x3f ,sizeof (dis)); dis[1 ]=0 ; priority_queue<pair<int ,int > >q; q.push ({0 ,1 }); while (q.size ()) { int u=q.top ().second; q.pop (); if (vis[u])continue ; vis[u]=1 ; for (int i=hed[u];i;i=nxt[i]) { int v=to[i]; int val=w[i]; if (!vis[v] && dis[v]>dis[u]+val) { dis[v]=dis[u]+val; q.push ({-dis[v],v}); } } } } signed main () { cin>>n>>m>>k>>e; for (int i=1 ;i<=e;i++) { int x,y,z; cin>>x>>y>>z; add (x,y,z); add (y,x,z); } cin>>d; for (int i=1 ;i<=d;i++) { int p,a,b; cin>>p>>a>>b; for (int l=a;l<=b;l++)stuck[p][l]=1 ; } for (int i=1 ;i<=n;i++) { for (int j=i;j<=n;j++) { memset (vis,0 ,sizeof (vis)); for (int p=1 ;p<=m;p++) { for (int day=i;day<=j;day++) { if (stuck[p][day])vis[p]=1 ; } } dij (); co[i][j]=dis[m]; } } memset (dp,0x3f ,sizeof (dp)); dp[0 ]=0 ; for (int i=1 ;i<=n;i++) { dp[i]=1LL *co[1 ][i]*i; for (int j=0 ;j<i;j++) { dp[i]=min (dp[i],dp[j]+1LL *co[j+1 ][i]*(i-j)+k); } } cout<<dp[n]; }
题意:给出一个 $n$ 个点 $m$ 条边的带权无向联通图。定义一条路径的权重为:
$$
\sum w_{i}-max\ w_i+min\ w_i
$$
求 $1-n$ 的最短路。
原来题还能这么做
考虑将题目弱化,题目要求删除最大的边,加上最小的边,将题目若化为加上一条边,删除一条边,在Dijkstra算法下一定会贪心选择最优的,即会删除最大的,加上最小的。因此将 $d$ 数组多加上两维状态即可。在加入优先队列时要将情况考虑完。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include <bits/stdc++.h> using namespace std;#define int long long int n,m;int cnt,hed[400005 ],nxt[400005 ],edg[400005 ],to[400005 ];int vis[400005 ][2 ][2 ];int dis[400005 ][2 ][2 ];void add (int x,int y,int w) { to[++cnt]=y; edg[cnt]=w; nxt[cnt]=hed[x]; hed[x]=cnt; } signed main () { ios_base::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); cin>>n>>m; for (int i=1 ;i<=m;i++) { int x,y,z; cin>>x>>y>>z; add (x,y,z); add (y,x,z); } priority_queue<pair<int ,pair<int ,pair<int ,int > > > > q; memset (dis,0x3f ,sizeof (dis)); dis[1 ][0 ][0 ]=0 ; q.push ({0 ,{1 ,{0 ,0 }}}); while (q.size ()) { int u=q.top ().second.first; int a1=q.top ().second.second.first; int a2=q.top ().second.second.second; q.pop (); if (vis[u][a1][a2])continue ; vis[u][a1][a2]=1 ; for (int i=hed[u];i;i=nxt[i]) { int v=to[i]; int w=edg[i]; if (dis[v][a1][a2]>dis[u][a1][a2]+w) { dis[v][a1][a2]=dis[u][a1][a2]+w; q.push ({-dis[v][a1][a2],{v,{a1,a2}}}); } if (a1==0 && dis[v][1 ][a2]>dis[u][0 ][a2]) { dis[v][1 ][a2]=dis[u][0 ][a2]; q.push ({-dis[v][1 ][a2],{v,{1 ,a2}}}); } if (a2==0 && dis[v][a1][1 ]>dis[u][a1][0 ]+2 *w) { dis[v][a1][1 ]=dis[u][a1][0 ]+2 *w; q.push ({-dis[v][a1][1 ],{v,{a1,1 }}}); } if (a1==0 && a2==0 && dis[v][1 ][1 ]>dis[u][0 ][0 ]+w) { dis[v][1 ][1 ]=dis[u][0 ][0 ]+w; q.push ({-dis[v][1 ][1 ],{v,{1 ,1 }}}); } } } for (int i=2 ;i<=n;i++)cout<<dis[i][1 ][1 ]<<" " ; return 0 ; }
题意:$n$ 个城市,$m$ 条有向边。要从城市1开车到城市 $n$,油箱容量为 $c$,中途有些城市可以加油,也有些城市支持倒卖,而四级只能倒卖一次油,问最多能赚多少钱。
跑两遍最短路,第一次从1跑向 $n$ ,记录到达每个城市时(如果当前可以加油,那么还剩 $c$)最多还剩多少油;第二次从 $n$ 跑向1,记录到达每个城市时最少消耗多少油(如果当前可以加油,那么消耗 $c$)。然后枚举每一个可以卖的节点,得到的钱就是 $(left-cost)\times price$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <map> #include <queue> #include <stack> #include <cmath> #include <vector> #include <bitset> #define inf 0x3f3f3f3f #define Inf 0x3FFFFFFFFFFFFFFFLL #define eps 1e-6 #define pi acos(-1.0) using namespace std;typedef long long ll;const int maxn=1000 +10 ;const int maxm=100000 +10 ;struct Edge { int v,w,next; Edge (){} Edge (int v,int w,int next):v (v),w (w),next (next){} }edges[maxm<<1 ]; int head[maxn],dmax[maxn],dmin[maxn],nEdge;int fuel[maxn],sell[maxn],n,m,C;bool inq[maxn];void AddEdges (int u,int v,int w) { edges[++nEdge]=Edge (v,w,head[u]); head[u]=nEdge; edges[++nEdge]=Edge (u,w,head[v]); head[v]=nEdge; } void fmax (int s) { memset (dmax,0xff ,sizeof (dmax)); memset (inq,0 ,sizeof (inq)); queue<int >q; q.push (s); dmax[s]=C; while (!q.empty ()) { int u=q.front ();q.pop (); inq[u]=false ; for (int k=head[u];k!=-1 ;k=edges[k].next) { if (dmax[u]<edges[k].w||(k&1 )) continue ; int v=edges[k].v; if (dmax[v]<dmax[u]-edges[k].w) { if (fuel[v]) dmax[v]=C; else dmax[v]=dmax[u]-edges[k].w; if (!inq[v]) {inq[v]=true ;q.push (v);} } } } } void fmin (int s) { memset (dmin,0x3f ,sizeof (dmin)); memset (inq,0 ,sizeof (inq)); queue<int >q; q.push (s); dmin[s]=0 ; while (!q.empty ()) { int u=q.front ();q.pop (); inq[u]=false ; for (int k=head[u];k!=-1 ;k=edges[k].next) { if (dmin[u]+edges[k].w>C||(k%2 ==0 )) continue ; int v=edges[k].v; if (dmin[v]>dmin[u]+edges[k].w) { if (fuel[v]) dmin[v]=0 ; else dmin[v]=dmin[u]+edges[k].w; if (!inq[v]) {inq[v]=true ;q.push (v);} } } } } int main () { while (~scanf ("%d%d%d" ,&n,&m,&C)) { memset (head,0xff ,sizeof (head)); memset (fuel,0 ,sizeof (fuel)); memset (sell,0 ,sizeof (sell)); nEdge=-1 ; int u,v,w; for (int i=0 ;i<m;++i) { scanf ("%d%d%d" ,&u,&v,&w); AddEdges (u,v,w); } int P,Q; scanf ("%d" ,&P); while (P--) { scanf ("%d" ,&u); fuel[u]=1 ; } scanf ("%d" ,&Q); while (Q--) { scanf ("%d%d" ,&u,&w); sell[u]=w; } fmax (1 ); fmin (n); int ans=0 ,tmp; for (int i=1 ;i<=n;++i) if (sell[i]&&dmax[i]!=-1 &&dmin[i]!=inf) { tmp=(dmax[i]-dmin[i])*sell[i]; ans=max (ans,tmp); } if (dmax[n]==-1 ) ans=-1 ; printf ("%d\n" ,ans); } return 0 ; }
题意:设一个图为一个具有层的无向图,可以从 $x$ 层的任一点移动到 $x+1$ 层的任意点,花费 $c$ ,在同一层移动不需要花费。另外还有 $m$ 条额外的边,花费为 $w$ ,计算点1到点 $n$ 的最短距离。
一个需要增点建图的题。
增点有很多方法,给出一种增点建图方法如下:相邻两层中间设两个节点,下层到上层用一个点,下层点汇聚到增点花费为 $c$ ,增点又连向上层所有点花费为0;上层到下层同理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 #include <bits/stdc++.h> using namespace std;const int N=300006 ;int n,m,c;vector<pair<int ,int > >g[N]; vector<int >lay[N]; int dis[N];int vis[N];void solve () { memset (vis,0 ,sizeof (vis)); cin>>n>>m>>c; for (int i=1 ;i<=n*2 +1 ;i++)vis[i]=0 ,g[i].clear (),lay[i].clear (); for (int i=1 ;i<=n;i++) { int x; cin>>x; lay[x].push_back (i); } for (int i=1 ;i<=m;i++) { int x,y,z; cin>>x>>y>>z; g[x].push_back ({y,z}); g[y].push_back ({x,z}); } int node=n+1 ; for (int i=1 ;i<n;i++) { if (!lay[i].empty ()&&!lay[i+1 ].empty ()) { for (int j=0 ;j<lay[i].size ();j++) { int u=lay[i][j]; g[u].push_back ({node,c}); } for (int j=0 ;j<lay[i+1 ].size ();j++) { int v=lay[i+1 ][j]; g[node].push_back ({v,0 }); } node++; } } for (int i=n;i>1 ;i--) { if (!lay[i].empty ()&&!lay[i-1 ].empty ()) { for (int j=0 ;j<lay[i].size ();j++) { int u=lay[i][j]; g[u].push_back ({node,c}); } for (int j=0 ;j<lay[i-1 ].size ();j++) { int v=lay[i-1 ][j]; g[node].push_back ({v,0 }); } node++; } } memset (dis,0x3f ,sizeof (dis)); dis[1 ]=0 ; priority_queue<pair<int ,int > > q; q.push ({0 ,1 }); while (q.size ()) { int u=q.top ().second; q.pop (); if (vis[u])continue ; vis[u]=1 ; for (auto [v,w]:g[u]) { if (vis[v])continue ; if (dis[v]>dis[u]+w) { dis[v]=dis[u]+w; q.push ({-dis[v],v}); } } } if (dis[n]==0x3f3f3f3f )cout<<"-1\n" ; else cout<<dis[n]<<endl; } int main () { ios_base::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); int t; cin>>t; for (int i=1 ;i<=t;i++)cout<<"Case #" <<i<<": " ,solve (); return 0 ; }
题意:给出一张$n$个点,$m$条边的带权无向图,多次询问,每次给出$u,v$,要求输出$u$到$v$的最短距离。$1\leq n\leq m\leq 10^5,m-n\leq20$。
注意到 $m-n\leq20$ ,即若删去至多21条边,这个图便是一棵树。对于一棵树,求两点的距离可用 $lca$ 求,即:
$$
d(i,j)=d[i]+d[j]-2\times d[lca(i,j)]
$$
那么对于非树边,最多涉及42个点,把这些点拿出来跑Dijkstra,对于任意一组询问,答案即为树边与涉及非树边的较小值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <bits/stdc++.h> using namespace std;typedef long long ll;typedef pair<ll,int > P;#define ft first #define sd second const int maxn=1e5 +10 ;const ll inf=1e15 ;int n,m;ll d[50 ][maxn]; vector<P> g[maxn]; bool vis[maxn];int qq[maxn],top=0 ;ll dist[maxn]; int dep[maxn],f[maxn][22 ];void dfs (int u,int fa) { f[u][0 ]=fa;vis[u]=1 ; for (int i=0 ;i<(int )g[u].size ();i++){ int v=g[u][i].sd;ll w=g[u][i].ft; if (v==fa)continue ; if (vis[v]){ qq[top++]=u,qq[top++]=v; } else { dep[v]=dep[u]+1 ; dist[v]=dist[u]+w; dfs (v,u); } } } int lca (int u,int v) { if (dep[u]<dep[v])swap (u,v); for (int i=19 ;i>=0 ;i--)if (dep[f[u][i]]>=dep[v])u=f[u][i]; if (u==v)return u; for (int j=19 ;j>=0 ;j--) if (f[u][j]!=f[v][j]) u=f[u][j],v=f[v][j]; return f[u][0 ]; } void dijkstra (int s) { for (int i=1 ;i<=n;i++)d[s][i]=inf; d[s][qq[s]]=0 ; priority_queue<P,vector<P>,greater<P> > pq; pq.push (P (0 ,qq[s])); while (!pq.empty ()){ P p=pq.top ();pq.pop (); int u=p.sd; if (d[s][u]<p.ft)continue ; for (int i=0 ;i<(int )g[u].size ();i++){ P now=g[u][i]; int v=now.sd; if (d[s][v]>d[s][u]+now.ft){ d[s][v]=d[s][u]+now.ft; pq.push (P (d[s][v],v)); } } } } int main () { scanf ("%d%d" ,&n,&m); for (int i=0 ;i<m;i++){ int u,v;ll w; scanf ("%d%d%lld" ,&u,&v,&w); g[u].push_back (P (w,v)); g[v].push_back (P (w,u)); } dep[1 ]=1 ; dfs (1 ,0 ); for (int j=1 ;j<=19 ;j++) for (int i=1 ;i<=n;i++) f[i][j]=f[f[i][j-1 ]][j-1 ]; sort (qq,qq+top);top=unique (qq,qq+top)-qq; for (int i=0 ;i<top;i++)dijkstra (i); int q; cin>>q; while (q--){ int u,v; scanf ("%d%d" ,&u,&v); ll ans=dist[u]+dist[v]-dist[lca (u,v)]*2 ; for (int i=0 ;i<top;i++){ ans=min (ans,d[i][u]+d[i][v]); } cout<<ans<<'\n' ; } return 0 ; }
题意:给一个有向图,每个点只能经过一次,边权都为1。现在要从1号点到 $n$ 号点,要通过将一些边权改为2来使所有的方案路径长度相等。输出修改后的所有边的长度。
前置知识:差分约束
从1号点到 $n$ 号点的所有方案数的路径长度相等,说明在路径会经过的点上,若存在一条边 $u\rightarrow v$ ,存在 $1\leq dis[v]-dis[u]\leq 2$ ,$dis[u]$ 表示点 $u$ 到点1的距离。那么就可以转换为差分约束的条件:
$$
dis[v]-dis[u]\leq2\ \Rightarrow add(u,v,2)
$$
$$
dis[v]-dis[u]\geq 1\ \Rightarrow dis[u]-dis[v]\leq-1\ \Rightarrow add(v,u,-1)
$$
建图跑spfa即可。
注意只在路径会经过的点上建边,要两遍dfs判断哪些点在路径上(从1开始遍历,从 $n$ 开始遍历,记录经过了两次的点)。因为不在路径上的点可能是不满足条件的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 #include <bits/stdc++.h> using namespace std;const int N=1005 ;int n,m;vector<pair<int ,int > > g[N]; vector<int > g1[N],g2[N]; int v1[N],v2[N];int valid[N];int num[N],d[N],vis[N];bool flag=1 ;struct bian { int u,v; }adj[5005 ]; void dfs1 (int u) { v1[u]=1 ; for (auto v:g1[u])if (!v1[v])dfs1 (v); } void dfs2 (int u) { v2[u]=1 ; for (auto v:g2[u])if (!v2[v])dfs2 (v); } void spfa () { memset (d,0x3f ,sizeof (d)); d[1 ]=0 ; vis[1 ]=1 ; queue<int >q; q.push (1 ); num[1 ]++; while (q.size ()) { int u=q.front (); q.pop (); vis[u]=0 ; for (auto i:g[u]) { int v=i.first; int w=i.second; if (d[v]>d[u]+w) { d[v]=d[u]+w; if (!vis[v]) { q.push (v); vis[v]=1 ; num[v]++; if (num[v]>n) { flag=0 ; return ; } } } } } } int main () { cin>>n>>m; for (int i=1 ;i<=m;i++) { int x,y; cin>>x>>y; g1[x].push_back (y); g2[y].push_back (x); adj[i].u=x,adj[i].v=y; } dfs1 (1 ); dfs2 (n); valid[1 ]=1 ,valid[n]=1 ; for (int i=1 ;i<=n;i++) { if (v1[i] && v2[i])valid[i]=1 ; } for (int i=1 ;i<=m;i++) { int u=adj[i].u,v=adj[i].v; if (valid[u] && valid[v]) { g[u].push_back ({v,2 }); g[v].push_back ({u,-1 }); } } spfa (); if (!flag) { cout<<"No" ; return 0 ; } cout<<"Yes\n" ; for (int i=1 ;i<=m;i++) { int u=adj[i].u,v=adj[i].v; if (valid[u] && valid[v]) { cout<<d[v]-d[u]<<endl; } else cout<<"1\n" ; } }
题意:给定一个数组,求最长的子序列满足 $|A_i-A_{i-1}|\leq d$ 。$n\leq10^5,\ d\leq10^8$
前置知识:数据结构加速dp
若是普通dp,时间复杂度为 $O(n^2)$ ,显然无法满足,考虑用线段树加速 dp。
本质上是求在 $dp[i]$ 时,求出一个满足 $a[i]-d\leq a[j]\leq a[i]+d$ 的最大的 $dp[j]$ ,所以就用线段树维护 $a[j]$ 的最值。
注意数据范围,需要离散化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <bits/stdc++.h> using namespace std;int n,d;int a[100005 ],lsh[100005 ];struct node { int l,r,mx; }tr[800080 ]; int cnt;void build (int p,int l,int r) { tr[p].l=l; tr[p].r=r; if (l==r)return ; int mid= l+r >> 1 ; build (p*2 ,l,mid); build (p*2 +1 ,mid+1 ,r); } int query (int p,int l,int r) { if (tr[p].l>=l && tr[p].r<=r) { return tr[p].mx; } int mid= tr[p].l+tr[p].r >> 1 ; int s1=0 ,s2=0 ; if (mid>=l)s1=query (p*2 ,l,r); if (mid<r)s2=query (p*2 +1 ,l,r); return max (s1,s2); } void update (int p,int pos,int x) { if (tr[p].l==pos && tr[p].r==pos) { tr[p].mx=x; return ; } int mid= tr[p].l+tr[p].r >> 1 ; if (mid>=pos)update (p*2 ,pos,x); if (mid<pos)update (p*2 +1 ,pos,x); tr[p].mx=max (tr[p*2 ].mx,tr[p*2 +1 ].mx); } void solve () { memset (tr,0 ,sizeof (tr)); for (int i=1 ;i<=n;i++)cin>>a[i],lsh[i]=a[i]; sort (lsh+1 ,lsh+n+1 ); cnt=unique (lsh+1 ,lsh+n+1 )-lsh-1 ; build (1 ,1 ,cnt); int ans=0 ; for (int i=1 ;i<=n;i++) { int pos=lower_bound (lsh+1 ,lsh+cnt+1 ,a[i])-lsh; int l=lower_bound (lsh+1 ,lsh+cnt+1 ,a[i]-d)-lsh; int r=upper_bound (lsh+1 ,lsh+cnt+1 ,a[i]+d)-lsh-1 ; int x=query (1 ,l,r)+1 ; ans=max (ans,x); update (1 ,pos,x); } cout<<ans<<endl; } int main () { while (cin>>n>>d)solve (); return 0 ; }
每个奶牛都有自己喜欢的颜色。其中,如果奶牛 $A$ 和奶牛 $B$ 都仰慕一头喜欢颜色 $C$ 的奶牛,那么它们喜欢的颜色需要相同。在以上条件下,找到一种奶牛喜欢颜色的分配方案,可以让奶牛喜欢的颜色尽量不相同,输出字典序最小的答案。
若将 $A$ 仰慕 $B$ 建边 $B\rightarrow A$ ,那么在最后的图中,每个点的儿子应该归为一类,即需要合并节点。在合并节点时,通过启发式合并(将儿子少的加给儿子多的)将时间复杂度降为 $O(logn)$ ;每次合并 $f,s$ 时,将 $son[fa[s]]$ 所有儿子的父亲都设为 $fa[f]$ ,并且将集合 $fa[s]$ 的所有出边都连到 $fa[f]$ 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> using namespace std;int n,m;vector<int >adj[200005 ],son[200005 ]; int fa[200005 ];queue<int >pq; void merge (int f,int s) { f=fa[f],s=fa[s]; if (son[f].size ()<son[s].size ())swap (f,s); for (auto i:son[s])fa[i]=f,son[f].push_back (i); for (auto i:adj[s])adj[f].push_back (i); if (adj[f].size ()>1 )pq.push (f); } int main () { cin>>n>>m; for (int i=1 ;i<=m;i++) { int a,b; cin>>a>>b; adj[a].push_back (b); } for (int i=1 ;i<=n;i++) { fa[i]=i; son[i].push_back (i); if (adj[i].size ()>1 )pq.push (i); } while (pq.size ()) { int u=pq.front (); pq.pop (); while (adj[u].size ()>1 ) { int lst=adj[u][adj[u].size ()-1 ]; adj[u].pop_back (); if (fa[lst]!=fa[adj[u][adj[u].size ()-1 ]]) { merge (lst,adj[u][adj[u].size ()-1 ]); } } } int cnt=0 ; int group[200005 ]={0 }; for (int i=1 ;i<=n;i++) { if (!group[fa[i]]) { group[fa[i]]=++cnt; } } for (int i=1 ;i<=n;i++)cout<<group[fa[i]]<<endl; }
题意:一共有 $n$ 个数,给出 $m$ 个条件形如 $i,j,s$ 表示 $a_i+a_{i+1}+…+a_j=s$ 。判断有多少个条件与之前的条件矛盾。
前置知识:扩展域与边带权并查集
每个条件可简化为 $sum[j]-sum[i-1]=s$ ,可用带边权的并查集判断矛盾。
在合并节点时,可画图理解:$fa[fy]=fx,\ d[fy]=s+d[x]-d[y]$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;int n,m,ans;int fa[200005 ];int d[200005 ];int getfa (int x) { if (x==fa[x])return x; int rt=getfa (fa[x]); d[x]+=d[fa[x]]; return fa[x]=rt; } int main () { while (cin>>n>>m) { for (int i=0 ;i<=n;i++)fa[i]=i,d[i]=0 ; ans=0 ; for (int i=1 ;i<=m;i++) { int x,y,w; cin>>x>>y>>w; x--; int fx=getfa (x); int fy=getfa (y); if (fx==fy) { if (d[y]-d[x]!=w)ans++; } else { fa[fy]=fx; d[fy]=w+d[x]-d[y]; } } cout<<ans; } return 0 ; }
题意:给定一个图,每个边是黑的或白的,求恰好有 k 条黑边的最小生成树。
好难想啊
先跑一遍最小生成树,会发现白边要么多了要么少了。
若白边少了,说明白边的权重较大,那么适当减去一个 $\Delta$ ,再跑最小生成树。
若白边多了,说明白边的权重较小,那么适当加上一个 $\Delta$ ,再跑最小生成树。
也就是二分 $\Delta$ ,找最短路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <bits/stdc++.h> using namespace std;struct bian { int u,v,w,c; }g[100005 ]; int wnum,sum,ans;int n,m,k;int fa[100005 ];bool cmp (bian x,bian y) { if (x.w==y.w)return x.c<y.c; return x.w<y.w; } int getfa (int x) { return fa[x] = fa[x]==x?x:getfa (fa[x]); } void check () { wnum=0 ; sum=0 ; sort (g+1 ,g+m+1 ,cmp); for (int i=1 ;i<=n;i++)fa[i]=i; for (int i=1 ;i<=m;i++) { int fx=getfa (g[i].u); int fy=getfa (g[i].v); if (fx==fy)continue ; fa[fy]=fx; sum+=g[i].w; if (g[i].c==0 )wnum++; } } int main () { int t=1 ; while (cin>>n>>m>>k) { for (int i=1 ;i<=m;i++) { cin>>g[i].u>>g[i].v>>g[i].w>>g[i].c; g[i].u++; g[i].v++; } int l=-120 ,r=120 ; while (l<r) { int mid= l+r >> 1 ; for (int i=1 ;i<=m;i++) { if (g[i].c==0 )g[i].w+=mid; } check (); for (int i=1 ;i<=m;i++) { if (g[i].c==0 )g[i].w-=mid; } if (k>wnum)r=mid; else l=mid+1 ,ans=sum-mid*k; } cout<<"Case " <<t++<<": " <<ans<<endl; } return 0 ; }
题意:给一个图,求出包含每一条边的最小生成树大小。
前置知识:直径与最近公共祖先
注意到一个很重要的性质:非严格次小生成树由最小生成树加上一条边并删去形成环中的一条边生成。
那么确定包含某一条边时,若这条边在最小生成树中,那么答案就是最小生成树;若这条边不在,那么就需要在最小生成树加上这条边之后构成的环中删去一条最大的边,构成包含这条边的最小生成树。
那么最后的问题就是如何求出构成的环中的最大边。假设新接入的边为 $u\rightarrow v$ ,那么需要找到 $u\rightarrow lca(u,v)$ 以及 $v\rightarrow lca(u,v)$ 路径上的最大值。最大值可以在 $lca$ 预处理中完成,在预处理 $fa[x][i]$ 时处理出 $mx[x][i]=max(mx[x][i-1],mx[fa[x][i-1]][i-1])$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 #include <bits/stdc++.h> using namespace std;#define int long long const int N=2e5 +5 ;int n,m;int t;struct bian { int u,v,w,id; }edg[N*2 ],edg1[N*2 ]; int cnt,hed[N],nxt[N*2 ],to[N*2 ],val[N*2 ];int fa[N][20 ],mx[N][20 ];int ft[N];int d[N];int ans[N];int mnd;bool cmp (bian x,bian y) { return x.w<y.w; } int getfa (int x) { return ft[x] = x==ft[x]?x:getfa (ft[x]); } void add (int x,int y,int w) { to[++cnt]=y; val[cnt]=w; nxt[cnt]=hed[x]; hed[x]=cnt; } void bfs () { queue<int >q; d[1 ]=1 ; q.push (1 ); while (q.size ()) { int u=q.front (); q.pop (); for (int i=hed[u];i;i=nxt[i]) { int v=to[i]; int w=val[i]; if (d[v])continue ; d[v]=d[u]+1 ; fa[v][0 ]=u; mx[v][0 ]=w; for (int j=1 ;j<=t;j++) { fa[v][j]=fa[fa[v][j-1 ]][j-1 ]; mx[v][j]=max (mx[v][j-1 ],mx[fa[v][j-1 ]][j-1 ]); } q.push (v); } } } int lca (int x,int y) { int tmp=0 ; if (d[x]>d[y])swap (x,y); for (int i=t;i>=0 ;i--) { if (d[fa[y][i]]>=d[x]) { tmp=max (tmp,mx[y][i]); y=fa[y][i]; } } if (x==y)return tmp; for (int i=t;i>=0 ;i--) { if (fa[x][i]!=fa[y][i]) { tmp=max (tmp,mx[x][i]); tmp=max (tmp,mx[y][i]); x=fa[x][i],y=fa[y][i]; } } return max (max (tmp,mx[x][0 ]),mx[y][0 ]); } signed main () { cin>>n>>m; t=(int )(log (n)/log (2 ))+1 ; for (int i=1 ;i<=m;i++) { cin>>edg[i].u>>edg[i].v>>edg[i].w; edg[i].id=i; edg1[i].u=edg[i].u; edg1[i].v=edg[i].v; edg1[i].w=edg[i].w; } for (int i=1 ;i<=n;i++)ft[i]=i; sort (edg+1 ,edg+m+1 ,cmp); for (int i=1 ;i<=m;i++) { int fx=getfa (edg[i].u); int fy=getfa (edg[i].v); if (fx==fy)continue ; mnd+=edg[i].w; ans[edg[i].id]=-1 ; ft[fy]=fx; add (edg[i].u,edg[i].v,edg[i].w); add (edg[i].v,edg[i].u,edg[i].w); } bfs (); for (int i=1 ;i<=m;i++) { int id=edg[i].id; if (ans[id]==-1 ) { ans[id]=mnd; continue ; } int tmp=lca (edg1[id].u,edg1[id].v); ans[id]=mnd-tmp+edg1[id].w; } for (int i=1 ;i<=m;i++)cout<<ans[i]<<endl; return 0 ; }
题意:给定一棵树,求有多少种集合,满足集合内任两点的距离都是直径。
由直径重要性质知在无负权边的树上,所有直径的中点重合。那么考虑先找出一条直径,确定中点。
若中点为节点 $p$ ,那么统计 $p$ 的每个儿子有多少条满足长度要求的链,最后答案为 :
$$
(tot_1+1)\times (tot_2+1)\times …\times (tot_n+1)-(tot_1+tot_2+…+tot_n+1)
$$
若中点在一条边 $(u,v)$ 上,那么答案为 $u,v$ 子树中深度为 $diameter/2$ 的点的数量之积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #include <bits/stdc++.h> using namespace std;#define int long long const int N=2e5 +5 ;const int mod=998244353 ;int n;int cnt,hed[N*2 ],nxt[N*2 ],to[N*2 ];int d[N],f[N];int dmx,vt,zj,tot,bz;void add (int x,int y) { to[++cnt]=y; nxt[cnt]=hed[x]; hed[x]=cnt; } void dfs (int u,int fa) { for (int i=hed[u];i;i=nxt[i]) { int v=to[i]; if (v==fa)continue ; d[v]=d[u]+1 ; f[v]=u; if (d[v]>dmx) { dmx=d[v]; vt=v; } dfs (v,u); } } void dfs2 (int u,int fa,int st) { if (st==bz)tot++; for (int i=hed[u];i;i=nxt[i]) { if (to[i]==fa)continue ; dfs2 (to[i],u,st+1 ); } } signed main () { ios_base::sync_with_stdio (false ); cin>>n; for (int i=1 ;i<n;i++) { int x,y; cin>>x>>y; add (x,y); add (y,x); } dmx=0 ; dfs (1 ,0 ); int st=vt; dmx=0 ;memset (d,0 ,sizeof (d)); dfs (vt,0 ); int ed=vt; zj=dmx; if (zj%2 ==0 ) { int cnt=0 ; int bk; for (int i=ed;i;i=f[i]) { cnt++; if (cnt==zj/2 +1 ) { bk=i; break ; } } int ans=1 ; bz=zj/2 -1 ; for (int i=hed[bk];i;i=nxt[i]) { int v=to[i]; tot=0 ; dfs2 (v,bk,0 ); ans=(ans*(tot+1 ))%mod; } tot=0 ; bz++; dfs2 (bk,0 ,0 ); ans=(ans+mod-(tot+1 ))%mod; cout<<ans; } else { int cnt=0 ; int bk1,bk2; for (int i=ed;i;i=f[i]) { cnt++; if (cnt==zj/2 +1 ) { bk1=i;bk2=f[i]; break ; } } bz=zj/2 ; int ans=1 ; tot=0 ; dfs2 (bk1,bk2,0 ); ans=ans*tot%mod; tot=0 ; dfs2 (bk2,bk1,0 ); ans=ans*tot%mod; cout<<ans; } }
题意:有 $n$ 个人,$m$ 个事件,若输入格式为 $1\ x\ y$ 则表示 $x$ 成为了 $y$ 的上司,若为 $2\ x$ 则表示从 $x$ 开始开始上传从1开始编号的一份文件。每次询问某一个人有没有读过某一号文件。
首先用并查集简单记录员工和最高上司的关系,每有一个上司关系就连一条边。对于每一份文件,只需要存储文件开始的位置和结束的位置。最后查询时,可用 $lca$ 查询:若文件起始位置为 $u,v$ ,查询 $p$ ,那么一定有 $lca(u,p)=p\ and\ lca(p,v)=v$ 或者 $lca(u,p)=u\ and\ lca(p,v)=p$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include <bits/stdc++.h> using namespace std;const int N=1e5 +5 ;int n,m,t;int cnt,hed[N*2 ],nxt[N*2 ],to[N*2 ];int ft[N];int fa[N][30 ];int d[N];int tot;int c;struct shit { int em,num; }q[N]; pair<int ,int >mp[N]; void add (int x,int y) { to[++cnt]=y; nxt[cnt]=hed[x]; hed[x]=cnt; } int getfa (int x) { return ft[x] = x==ft[x]?x:getfa (ft[x]); } void merge (int s,int f) { s=getfa (s); f=getfa (f); ft[s]=f; } void bfs (int st) { queue<int >pq; d[st]=1 ; pq.push (st); while (pq.size ()) { int u=pq.front (); pq.pop (); for (int i=hed[u];i;i=nxt[i]) { int v=to[i]; if (d[v])continue ; d[v]=d[u]+1 ; fa[v][0 ]=u; for (int j=1 ;j<=t;j++)fa[v][j]=fa[fa[v][j-1 ]][j-1 ]; pq.push (v); } } } int lca (int x,int y) { if (d[x]>d[y])swap (x,y); for (int i=t;i>=0 ;i--)if (d[fa[y][i]]>=d[x])y=fa[y][i]; if (x==y)return x; for (int i=t;i>=0 ;i--)if (fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i]; return fa[x][0 ]; } int main () { cin>>n>>m; t=(int )(log (n)/log (2 ))+1 ; for (int i=1 ;i<=n;i++)ft[i]=i; for (int i=1 ;i<=m;i++) { int op; cin>>op; if (op==1 ) { int x,y; cin>>x>>y; add (x,y); add (y,x); merge (x,y); } if (op==2 ) { int x; cin>>x; int fx=getfa (x); mp[++tot].first=x,mp[tot].second=fx; } if (op==3 ) { cin>>q[++c].em>>q[c].num; } } for (int i=1 ;i<=n;i++) { if (!d[i])bfs (i); } for (int i=1 ;i<=c;i++) { int em=q[i].em,num=q[i].num; int x=mp[num].first,fx=mp[num].second; if (getfa (ft[em]) != getfa (ft[x]))cout<<"NO\n" ; else { if (lca (x,em)==em && lca (em,fx)==fx || lca (x,em)==x && lca (em,fx)==em)cout<<"YES\n" ; else cout<<"NO\n" ; } } return 0 ; }
题意:有四个点围成一圈,从2号点出发,最后要回到2号点。求至少路程为 $S$ 时,走过的最短路程。
前置知识:同余最短路
在到达2号点时,可以通过走无数次的 $d=2\times min(w(2,1),w(2,3))$ 刷步数的同时回到2号点,那么将 $d$ 作为模数,处理到达2号点并且在模 $d$ 意义下达到的步数时走的最短步数,最后答案即是 $min(dis[x]+d\times t)$ 。
在求最短路时 $dis$ 数组开两维,另一位存到达该点且模值为 $x$ 的最短路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include <bits/stdc++.h> using namespace std;#define int long long int k,a,b,c,d;int f[5 ][100005 ];int mod;vector<pair<int ,int > > g[5 ]; void dij () { memset (f,0x3f ,sizeof (f)); f[2 ][0 ]=0 ; priority_queue<pair<int ,int > > q; q.push ({0 ,2 }); while (q.size ()) { int u=q.top ().second; int w=-q.top ().first; q.pop (); if (w>f[u][w%mod])continue ; for (auto i:g[u]) { int v=i.first; int tmp=i.second; if (f[v][(tmp+w)%mod] > tmp+w) { f[v][(tmp+w)%mod]=tmp+w; q.push ({-(tmp+w),v}); } } } } void solve () { cin>>k>>a>>b>>c>>d; for (int i=1 ;i<=4 ;i++)g[i].clear (); g[1 ].push_back ({2 ,a}); g[2 ].push_back ({1 ,a}); g[2 ].push_back ({3 ,b}); g[3 ].push_back ({2 ,b}); g[3 ].push_back ({4 ,c}); g[4 ].push_back ({3 ,c}); g[4 ].push_back ({1 ,d}); g[1 ].push_back ({4 ,d}); mod=min (a,b); mod*=2 ; dij (); int ans=0x3f3f3f3f3f3f3f3f ; for (int i=0 ;i<mod;i++) { if (f[2 ][i]>k)ans=min (ans,f[2 ][i]); else { int t=(k-f[2 ][i]-1 )/mod+1 ; int tmp=f[2 ][i]+t*mod; ans=min (ans,tmp); } } cout<<ans<<endl; } signed main () { int t; cin>>t; while (t--)solve (); }