迎开学水题狂欢赛(舞踏会[dp+三叉树],HH去散步[矩阵快速幂],排序[模拟],铁路旅行[线段树])
快速簡單記錄老師口胡(可能就我自己看得懂了吧…)
文章目錄
- T1:舞踏會
- title
- solution
- code
- T2:HH去散步
- title
- solution
- code
- T3:排序
- title
- solution
- code
- T4:鐵路旅行
- title
- solution
- code
T1:舞踏會
title
solution
對于三個人中間取中值的操作,我們可以把它弄到樹上去,搞成一個三叉樹
然后可以任意亂排不固定人的位置的話,也就意味著這個三叉樹的形態是多變的
接著我們來定義一下這個樹上的規則,對于點fafafa 而言,他的三個兒子的權值要滿足:左兒子<=中兒子<=右兒子,即ls≤ms≤rsls\le ms\le rsls≤ms≤rs
但是左兒子的右兒子就不一定也要小于fafafa的值,這個大小關系是不會傳遞祖宗八代都保持的哦!
這樣對于該點的答案權值,直接取中間兒子的值就可以了
可知從fafafa 開始一直往右走或者往下走,這一路上的值都應該大于等于fafafa,也就是說這條路經過了多少個點就必須要滿足多少個點的值>=fa>=fa>=fa,這個fafafa的值才可能成為答案
我們定義這些點為坑,需要填充
(如果本身就有值固定了,就需要看是否>=fa>=fa>=fa)
而從fafafa 開始一直往左走或者往下走,這一路上的值都應該小于等于fafafa
為了讓答案更大,我們就想要坑的個數越小越好,同時要保證左子樹也滿足要求
code
/* 序列的1~3位以第n+1位為父節點 表示1~3位的中值會移動到第n+1位 序列的4~6位以第n+2位為父節點 以此類推...... 可知點i的三個兒子分別為(i-n)*3-2, (i-n)*3-1,(i-n)*3 我們稱必須大于等于答案并且沒人的葉子節點為坑 顯然根到每個坑的路徑上沒有向左的邊 */ #include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define MAXN 100005 #define INF 0x3f3f3f3f //不能定義成0x7f7f7f7f因為后面min比較會涉及加法 //兩個int上界相加則會爆出int成為負的 struct node {int val, num, rk; }nobel[MAXN]; int n, m, N; int p[MAXN], v[MAXN], dp[MAXN << 1]; /* val能力值 num編號 rk排名 p[i]編號為i的位置(沒有就為0) v[i]位置為i的排名 dp[i]以i為根的子樹 最少坑的數量 n人數 N樹上節點總數 也是整棵樹的根 */ bool cmpVal ( node x, node y ) {return ( x.val == y.val ) ? x.num < y.num : x.val < y.val; }bool cmpNum ( node x, node y ) {return x.num < y.num; }void dfs ( int u, int ans ) {if ( u <= n ) {//u為葉子節點 if ( ! v[u] )//位置上沒人需要填一個坑dp[u] = 1;else if ( v[u] >= ans )//位置上有一個滿足條件的人則不需要填坑dp[u] = 0;else//不滿足條件->u不能成為右子樹或者中子樹(非法)dp[u] = INF;//dp關于ans呈單調遞增//ans越大->對v[u]的要求越高->dp[u]=0的難度就越高 }else {int t = u - n;for ( int i = t * 3 - 2;i <= t * 3;i ++ )dfs ( i, ans );dp[u] = INF;dp[u] = min ( dp[t * 3 - 2] + dp[t * 3 - 1], dp[u] );dp[u] = min ( dp[t * 3 - 2] + dp[t * 3], dp[u] );dp[u] = min ( dp[t * 3 - 1] + dp[t * 3], dp[u] );//dp關于ans呈單調遞增 } }int main() {scanf ( "%d %d", &n, &m );N = n + ( n >> 1 );//共有n/2個非葉節點//一共要淘汰n-1個人 每三個人當中淘汰兩個//一個非葉節點代表三進一//所以淘汰次數為(n-1)/2 非葉節點個數也應為(n-1)/2//n又保證是奇數所以(n-1)/2=n/2 for ( int i = 1;i <= n;i ++ ) {scanf ( "%d", &nobel[i].val );nobel[i].num = i;if ( i <= m ) scanf ( "%d", &p[i] );}sort ( nobel + 1, nobel + n + 1, cmpVal );for ( int i = 1;i <= n;i ++ )nobel[i].rk = i;//按照能力排序后的排名sort ( nobel + 1, nobel + n + 1, cmpNum );for ( int i = 1;i <= m;i ++ )v[p[i]] = nobel[i].rk;int l = 1, r = n;while ( l != r ) {int mid = ( l + r + 1 ) >> 1;dfs ( N, mid );int tot = 0;//能力值>=mid的人數 用來填坑的數量//tot關于mid呈單調遞減 //mid越大->rk>=mid難度越高->tot++越難觸發for ( int i = m + 1;i <= n;i ++ )if ( nobel[i].rk >= mid ) tot ++;if ( dp[N] <= tot )//坑的數量比填坑的數量小則可以實現l = mid;elser = mid - 1; }sort ( nobel + 1, nobel + n + 1, cmpVal );printf ( "%d\n", nobel[l].val );return 0; }T2:HH去散步
title
HH有個一成不變的習慣,喜歡飯后百步走。所謂百步走,就是散步,就是在一定的時間 內,走過一定的距離。 但是同時HH又是個喜歡變化的人,所以他不會立刻沿著剛剛走來的路走回。 又因為HH是個喜歡變化的人,所以他每天走過的路徑都不完全一樣,他想知道他究竟有多 少種散步的方法。 現在給你學校的地圖(假設每條路的長度都是一樣的都是1),問長度為t,從給定地 點A走到給定地點B共有多少條符合條件的路徑
輸入格式
第一行:五個整數N,M,t,A,B。其中N表示學校里的路口的個數,M表示學校里的 路的條數,t表示HH想要散步的距離,A表示散步的出發點,而B則表示散步的終點。 接下來M行,每行一組Ai,Bi,表示從路口Ai到路口Bi有一條路。數據保證Ai != Bi,但不保證任意兩個路口之間至多只有一條路相連接。 路口編號從0到N ? 1。 同一行內所有數據均由一個空格隔開,行首行尾沒有多余空格。沒有多余空行。 答案模45989。
輸出格式
一行,表示答案。
樣例
Sample Input
4 5 3 0 0
0 1
0 2
0 3
2 1
3 2
Sample Output
4
數據范圍與提示
對于30%的數據,N ≤ 4,M ≤ 10,t ≤ 10。 對于100%的數據,N ≤ 20,M ≤ 60,t ≤ 2^30,0 ≤ A,B < N。
solution
看到n,mn,mn,m跟ttt完全不在一個量級的時候,一般都會想矩陣加速等玩意兒
假設原矩陣A[1][i]A[1][i]A[1][i]表示從起點走到iii點的方案數,加速矩陣B[i][j]B[i][j]B[i][j]表示i?>ji->ji?>j存在一條有向邊可以這么走
注意無向邊拆開的兩條邊不要連在一起
由于起點aaa可以一開始任意走連出去的邊,所以它沒有加速矩陣的統一,我們就單獨算一次,加速矩陣就少做一次即可
code
#include <cstdio> #include <cstring> #include <iostream> using namespace std; #define MAXN 150 #define mod 45989 int cnt = 1, result;struct Matrix {int c[MAXN][MAXN];Matrix () {memset ( c, 0, sizeof ( c ) );}Matrix operator * ( const Matrix &a ) const {Matrix ans;for ( int i = 1;i <= cnt;i ++ )for ( int j = 1;j <= cnt;j ++ )for ( int k = 1;k <= cnt;k ++ )ans.c[i][j] = ( ans.c[i][j] + 1ll * c[i][k] * a.c[k][j] % mod ) % mod;return ans;} }A, B;Matrix qkpow ( Matrix a, int b ) {Matrix ans;for ( int i = 1;i <= cnt;i ++ )ans.c[i][i] = 1;while ( b ) {if ( b & 1 ) ans = ans * a;a = a * a;b >>= 1;}return ans; }int n, m, t, a, b; int from[MAXN], to[MAXN];void add ( int u, int v ) {from[++ cnt] = u; to[cnt] = v;from[++ cnt] = v; to[cnt] = u; }int main() {scanf ( "%d %d %d %d %d", &n, &m, &t, &a, &b );a ++; b ++;for ( int i = 1;i <= m;i ++ ) {int u, v;scanf ( "%d %d", &u, &v );u ++; v ++;add ( u, v );}for ( int i = 1;i <= cnt;i ++ )if ( from[i] == a ) A.c[1][i] ++;for ( int i = 1;i <= cnt;i ++ )for ( int j = 1;j <= cnt;j ++ )if ( to[i] == from[j] && i != ( j ^ 1 ) && i != j )B.c[i][j] ++;B = qkpow ( B, t - 1 );A = A * B;for ( int i = 1;i <= cnt;i ++ )if ( to[i] == b ) result = ( result + A.c[1][i] ) % mod;printf ( "%d", result );return 0; }T3:排序
title
solution
有一個結論:操作順序的調換對最后的答案沒有影響
可以感性理解,自己畫畫反正就是舉不出反例(攤手無奈┓( ′?` )┏)
有了這個結論我們就可以知道,如果最后生成了一個不同的序列花費了xxx次操作,那么方案數就應該加上x!x!x!
接著題目的描述是每一次只能調換一次整體長度為2i2^i2i的兩段序列,也可以不換
下一輪就是操作長度為2i+12^{i+1}2i+1
也就是說如果本輪有超過222段不合法的序列,就無法完成,這個時候就可以回溯了
所以我們必須保證在iii層的時候,答案序列分成長度2i?12^{i-1}2i?1后的每一段內部都是有序的,這樣在進行本輪操作后才有可能成功,舉個栗子
12341\ 2\ 3\ 41?2?3?4
i=3:3214i=3:3\ 2\ 1\ 4i=3:3?2?1?4
可以發現必須要2i?12^{i-1}2i?1整段整段調換,無論如何都無法湊出最后答案
因為他們的內部并不按從小到大有序
而如果是這個栗子就可以湊出答案
12341\ 2\ 3\ 41?2?3?4
i=3:3412i=3:3\ 4\ 1\ 2i=3:3?4?1?2
由此可見必須先進行長度為111的調換操作,然后進行長度為222的,以此類推才有可能保證對于iii層而言,內部是有序的
剩下的就看代碼注釋吧…
code
#include <cstdio> #include <iostream> using namespace std; #define int long long #define MAXN 4100 int n, result; int A[MAXN];void change ( int idx1, int idx2, int len ) {for ( int i = 0;i < len;i ++ )swap ( A[idx1 + i], A[idx2 + i] ); } //注意:1234這種只是指類型,12是一種,34是一種 //比如2 3 || 9 10也符合12 34這種類型 void dfs ( int len, int x ) {if ( n == len ) {//所有類型的交換都已進行 滿足的方案就有x! int ans = 1;for ( int i = 1;i <= x;i ++ )ans *= i;result += ans;return;}int L = len << 1;int tot = 0;//不合法的個數int p[3];for ( int i = 1;i <= n;i += L ) {if ( A[i] % L != 1 || A[i + len] - A[i] != len ) {tot ++;if ( tot > 2 ) return;//超過兩個就無法完成 p[tot] = i;}}switch ( tot ) {case 0 : dfs ( L, x ); break;case 1 : {//21類型 需交換成為12 change ( p[1], p[1] + len, len );dfs ( L, x + 1 );change ( p[1], p[1] + len, len );break;}case 2 : {if ( A[p[1]] % L == 1 && A[p[2]] % L == 1 ) {//14 32類型 change ( p[1] + len, p[2] + len, len );dfs ( L, x + 1 );//交換成為12 34 change ( p[1] + len, p[2] + len, len );change ( p[1], p[2], len );dfs ( L, x + 1 );//交換成為34 21 change ( p[1], p[2], len );}else if ( A[p[1]] % L == 1 && A[p[1] + len] % L == 1 ) {if ( A[p[2] + len] - A[p[1] + len] == len ) {//13 24類型 (只能用4-3或者2-1判斷類型!不能用3-2)//比如2 5 3 6也是符合13 24類型,這個時候5-3就≠1)//下面的判斷同理change ( p[1] + len, p[2], len );dfs ( L, x + 1 );//交換成為12 34 change ( p[1] + len, p[2], len );}}else if ( A[p[2]] % L == 1 && A[p[2] + len] % L == 1 ) {//42 31類型 if ( A[p[1]] - A[p[2]] == len ) {//這個判斷等價于A[p[1]+len]-A[p[2]+len]=lenchange ( p[1], p[2] + len, len );dfs ( L, x + 1 );//交換成為12 34 change ( p[1], p[2] + len, len );}}break;}} }signed main() {scanf ( "%lld", &n );n = 1 << n;for ( int i = 1;i <= n;i ++ )scanf ( "%lld", &A[i] );dfs ( 1, 0 );printf ( "%lld", result );return 0; }T4:鐵路旅行
title
solution
首先很容易想到:
對于第i條路而言,假設第i條路走了j次,那么我們的抉擇就是:
min(A[i]?j,B[i]?j+C[i])min(A[i]*j,B[i]*j+C[i])min(A[i]?j,B[i]?j+C[i])
接著發現題目的鐵路是一條單鏈且順次連接
意味著對于點i,ji,ji,j,鐵路i?ji-ji?j都要加1
這種區間操作我們很容易就想到了線段樹操作
最后再單點查詢每個點走的次數即可
(實在很簡單,我都不會口胡了)
code
#include <cstdio> #include <iostream> using namespace std; #define MAXN 100005 #define int long long int n, m, result; int a[MAXN], b[MAXN], c[MAXN], p[MAXN]; int tree[MAXN << 2], flag[MAXN << 2];void pushdown ( int t, int l, int r ) {if ( ! flag[t] ) return;int mid = ( l + r ) >> 1;tree[t << 1] += flag[t] * ( mid - l + 1 );tree[t << 1 | 1] += flag[t] * ( r - mid );flag[t << 1] += flag[t];flag[t << 1 | 1] += flag[t];flag[t] = 0; }void add ( int t, int l, int r, int L, int R ) {if ( L <= l && r <= R ) {tree[t] += ( r - l + 1 );flag[t] ++;return;}pushdown ( t, l, r );int mid = ( l + r ) >> 1;if ( L <= mid ) add ( t << 1, l, mid, L, R );if ( mid < R ) add ( t << 1 | 1, mid + 1, r, L, R );tree[t] = tree[t << 1] + tree[t << 1 | 1]; }int query ( int t, int l, int r, int id ) {if ( l == r ) return tree[t];pushdown ( t, l, r );int mid = ( l + r ) >> 1;if ( id <= mid ) return query ( t << 1, l, mid, id );else return query ( t << 1 | 1, mid + 1, r, id ); }signed main() {scanf ( "%lld %lld", &n, &m );for ( int i = 1;i <= m;i ++ )scanf ( "%lld", &p[i] );for ( int i = 1;i < m;i ++ ) {int minn = min ( p[i], p[i + 1] );int maxx = max ( p[i], p[i + 1] );add ( 1, 1, n, minn, maxx - 1 );}for ( int i = 1;i < n;i ++ )scanf ( "%lld %lld %lld", &a[i], &b[i], &c[i] );for ( int i = 1;i < n;i ++ ) {int tot = query ( 1, 1, n, i );if ( tot * ( a[i] - b[i] ) < c[i] )result += tot * a[i];elseresult += tot * b[i] + c[i];}printf ( "%lld", result );return 0; }總結
以上是生活随笔為你收集整理的迎开学水题狂欢赛(舞踏会[dp+三叉树],HH去散步[矩阵快速幂],排序[模拟],铁路旅行[线段树])的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux编程入门教程(linux 编程
- 下一篇: 【CF813F】Bipartite Ch