夯实基础项目工程之图论——Uncle Bogdan and Country Happiness,Graph Coloring,How Many Paths?,Array Differentiation
文章目錄
- 做題情況項目報告
- Uncle Bogdan and Country Happiness
- Graph Coloring
- How Many Paths?
- Array Differentiation
做題情況項目報告
T1,T3T1,T3T1,T3一眼題,在實現上,T3T3T3耗時略長(有些情況未考慮到位)
T4T4T4感覺題,一眼隱隱約約有正解的思路
T2T2T2感覺題,思考到二分圖性質后,并未往DPDPDP上想
Uncle Bogdan and Country Happiness
CD1388C
rating:1800 簡單題
給出一顆根節點為111的樹,對于每個節點iii,有pip_ipi?個人的家在節點iii上
一開始所有人都在根節點上,然后每個人會往家沿著最短路走
每個人出發時有一個心情,可能是好心情也可能是壞心情,在經過一條邊時,心情可能由好變壞,但是不可能由壞變好
每個點有一個幸福檢測器,最后的檢測結果為:所有經過該節點的人中,好心情的人數減壞心情的人數
現在給出hih_ihi?,問有沒有可能最后每個節點的檢測結果恰好為hih_ihi?
solution
observation1 : 這是一棵以111為根的樹,經過每個點的人數是固定的,即為子樹內居住人數大小,記為sizi\rm siz_isizi?
observation2 : 由于心情變化規則可知,父親的壞心情人數一定不小于直系兒子壞心情人數和
所以直接dfs樹一遍,根據good+bad=sizi,good?bad=hi\rm good+bad=siz_i,good-bad=h_igood+bad=sizi?,good?bad=hi?,可以求得經過每個點時的好壞心情人數,然后根據壞心情人數是否夠判斷
#include <cstdio> #include <vector> using namespace std; #define maxn 100005 vector < int > G[maxn]; int T, n, m, flag; int p[maxn], h[maxn], bad[maxn], siz[maxn];void dfs1( int u, int fa ) {siz[u] = p[u];for( auto v : G[u] )if( v == fa ) continue;else dfs1( v, u ), siz[u] += siz[v];bad[u] = ( siz[u] - h[u] ) >> 1; }void dfs2( int u, int fa ) {int cnt = 0;for( auto v : G[u] ) {if( v == fa ) continue;else dfs2( v, u ), cnt += bad[v];}if( cnt + p[u] < bad[u] ) flag = 1; }int Fabs( int x ) { return x < 0 ? -x : x; }int main() {scanf( "%d", &T );next :while( T -- ) {scanf( "%d %d", &n, &m );flag = 0;for( int i = 1;i <= n;i ++ )G[i].clear(), siz[i] = bad[i] = 0;for( int i = 1;i <= n;i ++ )scanf( "%d", &p[i] );for( int i = 1;i <= n;i ++ )scanf( "%d", &h[i] );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}dfs1( 1, 0 );for( int i = 1;i <= n;i ++ )if( ( ( siz[i] - h[i] ) & 1 ) or ( siz[i] < Fabs( h[i] ) ) ) {printf( "NO\n" );goto next;}dfs2( 1, 0 );if( flag ) printf( "NO\n" );else printf( "YES\n" );}return 0; }
Graph Coloring
CF1354E
rating 2100: 中檔題
現有一張nnn個節點mmm條邊的無向圖,要求用{1,2,3}\{1,2,3\}{1,2,3}對每個點染色,使得相鄰的兩個點的權值的絕對值之差剛好為111
現在需要求出一個方案,使得一共染了n1n_1n1?個111,n2n_2n2?個222和n3n_3n3?個333,保證n1+n2+n3=nn_1+n_2+n_3=nn1?+n2?+n3?=n
無解則輸出NO\rm NONO;否則輸出YES\rm YESYES并輸出一個字符串,其中第iii位表示節點iii的顏色
solution
observation1: 各連通塊之間互不影響,所以可以分連通塊求解
observation2: 1/3只能和2相鄰,所以這張圖一定是個二分圖,并且2一定是全為一種顏色(黑色/白色)
dfs遍歷圖,判斷是否是二分圖
至于222必須是同種顏色,分連通塊就可以設計dpdpdp轉移了
dfs求出連通塊黑白顏色的個數及分別是哪些點
dpi,j:dp_{i,j}:dpi,j?: 到第iii個連通塊被標記222的點數個數為jjj是否存在一種方案可行
dpi,j=dpi?1,j?cnt0,i∣dpi?1,j?cnt1,idp_{i,j}=dp_{i-1,j-cnt_{0,i}}|dp_{i-1,j-{cnt_{1,i}}}dpi,j?=dpi?1,j?cnt0,i??∣dpi?1,j?cnt1,i??
只要最后dpcnt,n2=1dp_{cnt,n2}=1dpcnt,n2?=1就一定有解
剩下就是很簡單的根據dpdpdp倒著回去構造解了,先把所有的222找到,剩下的就是1,31,31,3無所謂了
倒回去看轉移的兩個dpdpdp哪個是可行的
#include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; #define maxn 5005 vector < int > G[maxn], G0[maxn], G1[maxn]; int n, m, n1, n2, n3, cnt; int c[maxn], ans[maxn]; bool dp[maxn][maxn]; bool vis[maxn], MS[maxn];void dfs1( int u ) {for( auto v : G[u] ) {if( ! ~ c[v] ) {c[v] = c[u] ^ 1;dfs1( v );}else if( c[u] == c[v] ) {printf( "NO\n" );exit( 0 );}} }void dfs2( int u ) {vis[u] = 1;if( c[u] ) G1[cnt].push_back( u );else G0[cnt].push_back( u );for( auto v : G[u] )if( ! vis[v] ) dfs2( v ); }int main() {scanf( "%d %d %d %d %d", &n, &m, &n1, &n2, &n3 );for( int i = 1, u, v;i <= m;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u ); }memset( c, -1, sizeof( c ) );for( int i = 1;i <= n;i ++ )if( ! ~ c[i] ) c[i] = 0, dfs1( i );for( int i = 1;i <= n;i ++ )if( ! vis[i] ) cnt ++, dfs2( i );dp[0][0] = 1;for( int i = 1;i <= cnt;i ++ )for( int j = 0;j <= n;j ++ ) {if( G0[i].size() <= j )dp[i][j] |= dp[i - 1][j - G0[i].size()];if( G1[i].size() <= j )dp[i][j] |= dp[i - 1][j - G1[i].size()];}if( ! dp[cnt][n2] ) return ! printf( "NO\n" );else printf( "YES\n" );int now = n2;for( int i = cnt;i;i -- ) {if( now >= G0[i].size() and dp[i - 1][now - G0[i].size()] )MS[i] = 0, now -= G0[i].size();else if( dp[i - 1][now - G1[i].size()] )MS[i] = 1, now -= G1[i].size();}for( int i = 1;i <= cnt;i ++ ) if( MS[i] )for( auto j : G1[i] )ans[j] = 2;else for( auto j : G0[i] )ans[j] = 2;for( int i = 1;i <= n;i ++ )if( ! ans[i] ) {if( now < n1 ) ans[i] = 1, now ++;else ans[i] = 3;}for( int i = 1;i <= n;i ++ ) printf( "%d", ans[i] );return 0; }
How Many Paths?
CF1547G
有一個有向圖,圖中含有nnn個點mmm條邊(邊中可能有自環,但是沒有重邊)
設頂點iii的答案為ansians_iansi?,則:
- 如果不存在一條111到iii的路徑,ansians_iansi?為000
- 如果只存在一條111到iii的路徑,ansians_iansi?為111
- 如果至少存在兩條111到iii的路徑,并且路徑數量是有限的,ansians_iansi?為222
- 如果存在無限條111到iii的路徑,ansians_iansi?為?1-1?1
注意路徑不必是簡單路徑
對于每一個1≤i≤n1 \leq i \leq n1≤i≤n,輸出ansians_iansi?
observation1: 不必是簡單路徑,所以可以到處繞彎
observation2: 只要存在一條路上有環,那么到iii的路徑就一定是無線條
自環特殊記錄一下
tarjan縮點,對縮點后的狀態建圖,同一個連通塊內的邊不再出現
連通塊內點數>1>1>1或者有自環標記說明該連通塊是環,連出去的邊,邊權?1-1?1;否則邊權000
從111所在的連通塊scc[1]開始跑堆優化dijkstra最短路
并記錄經過iii的次數vis[i]
如果iii的路徑長為inf證明到達不了,沒有路徑
如果路徑長為負數,說明有至少一條路徑上有環,則是無限條路徑
堆優化dijkstra最短路記錄經過次數后,直接判斷是否>1>1>1是不對的
因為寫法會導致,多次經過某點時,若沒有最短路更新是不會入隊,也就不會使得后面的點的經過次數增加
也就是說經過次數其實維護的是假的
在記錄一個pre[i]表示最短路iii由preipre_iprei?更新
最后倒著往前面找,然后vis[i]取一路上的最大值
這樣雖然維護的還是假的真正經過次數,但是就已經可以區別出是只經過一次還是更多次,我們也沒有必要找到真正經過了多少次iii
#include <stack> #include <queue> #include <cstdio> #include <vector> #include <iostream> using namespace std; #define inf 0x3f3f3f3f #define maxn 400005 struct node {int to, w;node(){}node( int To, int W ) { to = To, w = W; }bool operator < ( const node &t ) const { return w > t.w; } }; struct edge {int u, v;edge(){}edge( int U, int V ) { u = U, v = V; } }E[maxn]; priority_queue < node > q; stack < int > sta; vector < node > g[maxn]; vector < int > G[maxn], cnt[maxn]; int T, n, m, ip, tot; int dfn[maxn], low[maxn], dis[maxn], vis[maxn], scc[maxn], cir[maxn], pre[maxn], gone[maxn];void tarjan( int u ) {dfn[u] = low[u] = ++ ip, sta.push( u );for( auto v : G[u] ) {if( ! dfn[v] ) { tarjan( v ); low[u] = min( low[u], low[v] ); }else if( ! scc[v] ) low[u] = min( low[u], dfn[v] );}if( low[u] == dfn[u] ) {++ tot; int v;do {v = sta.top(), sta.pop(), scc[v] = tot;cnt[tot].push_back( v );} while( v != u );} }void find( int i ) {if( ! i or gone[i] ) return;else gone[i] = 1, find( pre[i] ), vis[i] = max( vis[i], vis[pre[i]] ); }int main() {scanf( "%d", &T );int Case;while( T -- ) {scanf( "%d %d", &n, &m );ip = tot = 0;while( ! q.empty() ) q.pop();while( ! sta.empty() ) sta.pop();for( int i = 1;i <= n;i ++ ) {cnt[i].clear(), G[i].clear(), g[i].clear();dfn[i] = vis[i] = cir[i] = scc[i] = gone[i] = pre[i] = 0, dis[i] = inf;}int Edge = 0;for( int i = 1, u, v;i <= m;i ++ ) {scanf( "%d %d", &u, &v );if( u == v ) cir[u] = 1;else G[u].push_back( v ), E[++ Edge] = edge( u, v );}for( int i = 1;i <= n;i ++ )if( ! dfn[i] ) tarjan( i );for( int i = 1;i <= Edge;i ++ )if( scc[E[i].u] ^ scc[E[i].v] ) {if( cir[E[i].u] or cir[E[i].v] or cnt[scc[E[i].u]].size() > 1 or cnt[scc[E[i].v]].size() > 1 )g[scc[E[i].u]].push_back( node( scc[E[i].v], -1 ) );elseg[scc[E[i].u]].push_back( node( scc[E[i].v], 0 ) );}dis[scc[1]] = cnt[scc[1]].size() > 1 ? -1 : 0;q.push( node( scc[1], 0 ) );vis[scc[1]] ++;while( ! q.empty() ) {int u = q.top().to; q.pop();for( int i = 0;i < g[u].size();i ++ ) {int v = g[u][i].to, w = g[u][i].w;vis[v] ++;if( dis[v] < 0 ) continue; if( dis[u] + w < dis[v] ) {dis[v] = dis[u] + w, pre[v] = u;q.push( node( v, dis[v] ) );}}}gone[scc[1]] = 1;for( int i = 1;i <= tot;i ++ ) find( i );for( int i = 1;i <= n;i ++ )if( dis[scc[i]] == inf ) printf( "0 " );else if( dis[scc[i]] < 0 or cir[i] or cnt[scc[i]].size() > 1 ) printf( "-1 " );else if( vis[scc[i]] > 1 ) printf( "2 " );else printf( "1 " );printf( "\n" );}return 0; }Array Differentiation
CF1552D
ttt組數據,每一組給定一個數組{an}\{a_n\}{an?},問是否存在這樣一個數組{bn}\{ b_n \}{bn?},對于所有i∈[1,n]i \in [1,n]i∈[1,n],都存在一組j、k∈[1,n]j、k\in[1,n]j、k∈[1,n],滿足ai=bj?bka_i=b_j-b_kai?=bj??bk?
1<=n<=10
solution
如果把bib_ibi?當成點權,建構一棵樹,使得aja_jaj?恰好等于樹上的邊權
那么最后剩下的aja_jaj?就會使得樹變成一張圖,并不是一個環,因為這是有向邊
但是如果這其中的某些邊取反,似乎就能成為一個環
看nnn的范圍那么小基本鎖定正解就是3n3^n3n暴搜,取反某些aia_iai?的值?ai-a_i?ai?,然后存在某些aaa的和為000
#include <cstdio> #include <algorithm> using namespace std; int T, n, flag; int a[15];void dfs( int x, int sum, int cnt ) {if( sum == 0 and cnt ) {flag = 0;return;}if( x > n ) return;if( flag ) dfs( x + 1, sum, cnt );if( flag ) dfs( x + 1, sum + a[x], cnt + 1 );if( flag ) dfs( x + 1, sum - a[x], cnt + 1 ); }int main() {scanf( "%d", &T );next :while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &a[i] );sort( a + 1, a + n + 1 );for( int i = 1;i < n;i ++ )if( a[i] == a[i + 1] ) {printf( "YES\n" );goto next;}n = unique( a + 1, a + n + 1 ) - a - 1;flag = 1;dfs( 1, 0, 0 );if( flag ) printf( "NO\n" );else printf( "YES\n" );}return 0; }
總結
以上是生活随笔為你收集整理的夯实基础项目工程之图论——Uncle Bogdan and Country Happiness,Graph Coloring,How Many Paths?,Array Differentiation的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一起爬山什么梗 你知道吗
- 下一篇: 数据结构二之线段树Ⅰ——Count Co