[CF/AT]各大网站网赛 体验部部长第一季度工作报告

文章目录

  • CodeForces
    • #712 (Div. 1)——1503
      • A. Balance the Bits
      • B. 3-Coloring
      • C. Travelling Salesman Problem
      • D. Flip the Cards
    • 108 (Rated for Div. 2)——1519
      • A. Red and Blue Beans
      • B. The Cake Is a Lie
      • C. Berland Regional
      • D. Maximum Sum of Products
      • E. Off by One
    • Codeforces Global Round 14——1515
      • A. Phoenix and Gold
      • B. Phoenix and Puzzle
      • C. Phoenix and Towers
      • D. Phoenix and Socks
      • E. Phoenix and Computers
      • F. Phoenix and Earthquake
      • G. Phoenix and Odometers
    • #720 (Div. 2)——1521
      • A. Nastia and Nearly Good Numbers
      • B. Nastia and a Good Array
      • C. Nastia and a Hidden Permutation
      • D. Nastia Plays with a Tree
      • E. Nastia and a Beautiful Matrix
    • Codeforces Global Round 13——1491
      • A. K-th Largest Value
      • B. Minimal Cost
      • C. Pekora and Trampoline
      • D. Zookeeper and The Infinite Zoo
      • E. Fib-tree
      • F. Magnets
    • Educational Codeforces Round 109 (Rated for Div. 2)——1525
      • A. Potion-making
      • B. Permutation Sort
      • C. Robot Collisions
      • D. Armchairs
    • #721 (Div. 2)——1527
      • A. And Then There Were K
      • B. Palindrome Game
      • C. Sequence Pair Weight
      • D. MEX Tree
      • E. Partition Game
    • #706 (Div. 2)——1496
      • A. Split it!
      • B. Max and Mex
      • C. Diamond Miner
      • D. Let's Go Hiking
    • #722 (Div. 1)——1528
      • A. Parsa's Humongous Tree
      • B. Kavi on Pairing Duty
      • C. Trees of Tranquillity
      • D. It's a bird! No, it's a plane! No, it's AaParsa!
      • E. Mashtali and Hagh Trees
    • #723 (Div. 2)——1526
      • A. Mean Inequality
      • B. I Hate 1111
      • C. Potions
      • D. Kill Anton
    • Deltix Round, Spring 2021 (Div. 1 + Div. 2)——1523
      • A. Game of Life
      • B. Lord of the Values
      • C. Compression and Expansion
      • D. Love-Hate
      • E. Crypto Lights
      • F. Favorite Game
  • AtCoder
    • 2021-05-16(ARC 119)
      • A - 119 × 2^23 + 1
      • B - Electric Board
      • C - ARC Wrecker 2
      • D - Grid Repainting 3
      • E - Pancakes
    • 2021-05-22(ABC 202)
      • A - Three Dice
      • B - 180°
      • C - Made Up
      • D - aab aba baa
      • E - Count Descendants
    • 2021-03-28(ARC 116)
      • A - Odd vs Even
      • B - Products of Min-Max
      • C - Multiple Sequences
      • D - I Wanna Win The Game
      • E - Spread of Information
    • 2021-05-23(ARC 120)
      • A - Max Add
      • B - Uniformly Distributed
      • C - Swaps 2
      • D - Bracket Score 2
      • E - 1D Party

CodeForces

咕咕暗杀任务名单

  • #712 (Div. 1)——1503 E

  • #712 (Div. 1)——1503 F

  • Educational Codeforces Round 108 (Rated for Div. 2)——1519 F

  • Global Round 14——1515 H

  • Global Round 14——1515 I

  • #703 (Div. 2)——1486 F

  • Codeforces Global Round 13——1491 G

  • Codeforces Global Round 13——1491 H

  • Codeforces Global Round 13——1491 I

  • 2021-05-16(ARC 119) F

  • Educational Codeforces Round 109——1525 E

  • Educational Codeforces Round 109——1525 F

  • #706——1496 E,F

  • ARC116—— F

  • #722 (Div. 1)——1528 F

  • ARC 120 F1,F2

  • #723 (Div. 2)——1526 E,F

  • Deltix Round, Spring 2021 (Div. 1 + Div. 2)——1523 G,H

#712 (Div. 1)——1503

A. Balance the Bits

0,10,10,1的个数都必须是偶数,且s1,sns_1,s_ns1,sn都必须为111,这个可以用来判无解

111的个数为cntcntcnt,前cnt/2cnt/2cnt/2111都填左括号,后cnt/2cnt/2cnt/2个都填右括号

至于000,则交叉着来,这一次aaa填了左括号,下一次就填右括号

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int T, n;
char s[maxn];int main() {scanf( "%d", &T );while( T -- ) {int cnt = 0;scanf( "%d %s", &n, s + 1 );for( int i = 1;i <= n;i ++ )cnt += ( s[i] == '1' );if( cnt & 1 || n & 1 || s[1] == '0' || s[n] == '0' ) printf( "NO\n" );else {printf( "YES\n" );bool flag = 1; int k = 1;string a, b;for( int i = 1;i <= n;i ++ )if( s[i] == '1' ) {a.push_back( ( ( k << 1 ) <= cnt ) ? '(' : ')' );b.push_back( ( ( k << 1 ) <= cnt ) ? '(' : ')' );k ++;}else {a.push_back( flag ? '(' : ')' );b.push_back( flag ? ')' : '(' );flag ^= 1;}cout << a << endl << b << endl;}}return 0;
}

B. 3-Coloring

看成n×nn\times nn×n的国际象棋棋盘,黑格子都填111,白格子都填222333就是来应急用的

颜色111被禁用,就用颜色222填白格子

颜色222被禁用,就用颜色111填黑格子

颜色333被禁用,随便填一个

应急是什么意思呢?——会出现某种颜色被禁用多次,导致另外一种颜色已经填完了其所需要填的各自

那么这个时候就只能用颜色333来填被禁用颜色负责的格子

显然,颜色333要么只出现在黑格子要么只出现在白格子

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 105
int n;
vector < pair < int, int > > mp[2];void print( int color, int x, int y ) {printf( "%d %d %d\n", color, x, y );fflush( stdout );
}int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )for( int j = 1;j <= n;j ++ )mp[( i + j ) & 1].push_back( make_pair( i, j ) );for( int i = 1, bidden;i <= n * n;i ++ ) {scanf( "%d", &bidden );int c, ip;switch( bidden ) {case 1 : {if( ! mp[1].empty() ) c = 2, ip = 1;else c = 3, ip = 0;break;}case 2 : {if( ! mp[0].empty() ) c = 1, ip = 0;else c = 3, ip = 1;break;}case 3 : {if( ! mp[0].empty() ) c = 1, ip = 0;else c = 2, ip = 1;break;}}print( c, mp[ip].back().first, mp[ip].back().second );mp[ip].pop_back();}return 0;
}

C. Travelling Salesman Problem

每一座城市都要访问,花费又是取较大值,意味着每一座城市至少都要支付包车费cic_ici,不妨一次性在手机上预约完

先把cic_ici提出来加起来,max(ci,aj−ai)=ci+max(0,aj−ai−ci)max(c_i,a_j-a_i)=c_i+max(0,a_j-a_i-c_i)max(ci,ajai)=ci+max(0,ajaici)

最后的目标变为尽量使得每一次坐车都是免费的(除了cic_ici外不再额外支付)

注意到如果aj<aia_j<a_iaj<ai,那么这次的坐车就一定是免费的

所以将所有城市按aaa从小到大排序

找一条从a1a_1a1(稳定度最小的城市)到ana_nan(稳定度最大的城市)的最短路,也就是额外的最小花费了

因为aaa从小到大排序后,就满足了一个性质:如果是从后往前巡逻,那么坐车一定是免费的

solution1

最古老的旅行商问题是用最短路解决的,这里不妨延续古人的智慧

考虑重新编码整张图,跑dijkstradijkstradijkstra最短路(请不要忘记带上你可爱的堆优化)

再老套的提前建完整张图再跑,巡警会因为承载过多而半路暴毙身亡显然不行

所以要寻找一种优化建图

  • 城市iii到城市i−1i-1i1,连边000

  • 找到最远的城市jjj,满足aj≤ai+cia_j\le a_i+c_iajai+ci,连边000(城市jjj一定是在iii的右边!)

    这一段区间的城市都是可以免费坐车的,二分找城市jjj

  • j+1j+1j+1连边aj+1−ai−cia_{j+1}-a_i-c_iaj+1aicijjj含义沿用上面一条

为哈只用跟j+1j+1j+1连,而考虑其他满足ak−ai−ci>0a_k-a_i-c_i>0akaici>0kkk??

假设jjj是距离iii最近的满足ai+ci<aja_i+c_i<a_jai+ci<aj的城市,kkk是满足该条件的更远的城市

  • 路径选择i−ki-kik

    花费为ak−ai−cia_k-a_i-c_iakaici

  • 路径选择i−j−ki-j-kijk

    花费为aj−ai−ci+ak−aj−cj=ak−ai−ci−cja_j-a_i-c_i+a_k-a_j-c_j=a_k-a_i-c_i-c_jajaici+akajcj=akaicicj

显然选择jjj这个中转城市更优,也就保证了建图的正确性

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
#define ll long long
#define Pair pair < ll, ll >
priority_queue < Pair > q;
int n;
ll a, c, ans;
Pair G[maxn];
bool vis[maxn];int work( ll x ) {int l = 1, r = n, pos;while( l <= r ) {int mid = ( l + r ) >> 1;if( G[mid].first <= x ) pos = mid, l = mid + 1;else r = mid - 1;}return pos;
}int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%lld %lld", &a, &c );G[i] = make_pair( a, c );ans += c;}sort( G + 1, G + n + 1 );q.push( make_pair( 0, 1 ) );while( ! q.empty() ) {int dis = q.top().first, id = q.top().second;q.pop();if( vis[id] ) continue;else vis[id] = 1;if( id == n ) {ans -= dis;break;}if( id > 1 ) q.push( make_pair( dis, id - 1 ) );int pos = work( G[id].first + G[id].second );q.push( make_pair( dis, pos ) );if( pos < n ) q.push( make_pair( dis - G[pos + 1].first + G[id].first + G[id].second, pos + 1 ) );}printf( "%lld\n", ans );return 0;
}

solution2

建立在从后往前巡逻一定是免费的基础上。∀i>1\forall_{i>1}i>1,最小化第一次到达aia_iai的最小成本

对应为∑i>1max(0,ai−aj−cj){j<i}\sum_{i>1}max(0,a_i-a_j-c_j)\ \{j<i\}i>1max(0,aiajcj) {j<i},每一次更新前iii座城市各自a+ca+ca+c的最大值

准确来说两种解法都是一样的思路,解法2是对解法1进行手玩性质的思路去掉了最短路代码,本质是一样的

采取更新最大值最小化成本的原因与解法1建图一致。选择中转城市一定更优

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
#define ll long long
#define Pair pair < ll, ll >
Pair G[maxn];
int n;
ll a, c, ans;int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%lld %lld", &a, &c );G[i] = make_pair( a, c );ans += c;}sort( G + 1, G + n + 1 );ll maxx = G[1].first + G[1].second;for( int i = 2;i <= n;i ++ ) {ans += max( 0ll, G[i].first - maxx );maxx = max( maxx, G[i].first + G[i].second );}printf( "%lld\n", ans );return 0;
}

D. Flip the Cards

卡牌两面一定分别属于[1,n],[n+1,2n][1,n],[n+1,2n][1,n],[n+1,2n]

如果有一张两个都在nnn以内,那么一定存在另一张两个都比nnn大,无法满足正面递增,反面递减

i,i∈[1,n]i,i∈[1,n]i,i[1,n]卡牌递增排序,flagiflag_iflagi表示该卡牌是否翻转了

对于每一个i,i∈[1,n]i,i∈[1,n]i,i[1,n],设fif_ifi为其对应的另一面数字

最后答案长相一定是一段{i,fi}\{i,f_i\}{i,fi},然后一段翻转接在后面fi,i{f_i,i}fi,i

用两个栈模拟维护fff递减

对于一个分界点iii,如果有min(fj,j≤i)>max(fj,i<j)min(f_j,j\le i)>max(f_j,i<j)min(fj,ji)>max(fj,i<j)

显然两段是独立的,此时就判断两端谁翻转接在另一段后面

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 400005
#define inf 0x3f3f3f3f
int n, ans;
bool flag[maxn];
int minn[maxn], maxx[maxn], f[maxn];int main() {scanf( "%d", &n );for( int i = 1, x, y;i <= n;i ++ ) {scanf( "%d %d", &x, &y );if( x <= n && y <= n ) return ! printf( "-1\n" );if( x <= n ) f[x] = y, flag[x] = 0;else f[y] = x, flag[y] = 1;}minn[0] = inf, maxx[n + 1] = -inf;for( int i = 1;i <= n;i ++ )minn[i] = min( minn[i - 1], f[i] );for( int i = n;i;i -- )maxx[i] = max( maxx[i + 1], f[i] );int top_s1 = inf, top_s2 = inf, tot_s1 = 0, tot_s2 = 0, rev_s1 = 0, rev_s2 = 0;for( int i = 1;i <= n;i ++ ) {if( top_s1 > top_s2 ) {swap( top_s1, top_s2 );swap( tot_s1, tot_s2 );swap( rev_s1, rev_s2 );} else;if( top_s1 > f[i] )top_s1 = f[i], tot_s1 ++, rev_s1 += flag[i];else if( top_s2 > f[i] )top_s2 = f[i], tot_s2 ++, rev_s2 += flag[i];elsereturn ! printf( "-1\n" );if( minn[i] > maxx[i + 1] ) {ans += min( rev_s1 + tot_s2 - rev_s2, rev_s2 + tot_s1 - rev_s1 );top_s1 = top_s2 = inf, tot_s1 = tot_s2 = rev_s1 = rev_s2 = 0;}}printf( "%d\n", ans );return 0;
}

108 (Rated for Div. 2)——1519

A. Red and Blue Beans

假设红豆荚数目更少

贪心地尽量多设置篮筐,每个篮筐恰有一个红豆荚,然后把蓝豆荚均分

如果这样都无法满足ddd,就一定无解了

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
int T, r, b, d;int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d %d", &r, &b, &d );if( ! d ) {if( r == b ) printf( "YES\n" );else printf( "NO\n" );continue;}int x = min( r, b ), y = max( r, b );int cnt = int( ceil( ( y - x ) * 1.0 / d ) );if( cnt <= x ) printf( "YES\n" );else printf( "NO\n" );}return 0;
}

B. The Cake Is a Lie

手完发现,花费其实与路径无关

简单证明一下,在(x,y)(x,y)(x,y)

右走(x,y+1)(x,y+1)(x,y+1),花费xxx,再下走(x+1,y+1)(x+1,y+1)(x+1,y+1),花费y+1y+1y+1

下走(x+1,y)(x+1,y)(x+1,y),花费yyy,再右走(x+1,y+1)(x+1,y+1)(x+1,y+1),花费x+1x+1x+1

总有个时刻会补上+1+1+1,时间先后问题罢了

#include <cstdio>
int T, n, m, k;int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d %d", &n, &m, &k );if( m - 1 + ( n - 1 ) * m != k ) printf( "NO\n" );else printf( "YES\n" );}return 0;
}

C. Berland Regional

直接将人按学校分批,单独考虑每个学校

队伍人数不足kkk就会组不成队,所以每个学校只会对小于等于学校人数的kkk贡献

并且贡献就看人数取模kkk余几,删除最差劲的最后几个,前缀和预处理就可以O(1)O(1)O(1)查贡献

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200005
#define ll long long
struct node {int u, s;
}p[maxn];
vector < ll > G[maxn];
int T, n;
ll ans[maxn];bool cmp( node x, node y ) {return x.s < y.s;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )G[i].clear(), ans[i] = 0;for( int i = 1;i <= n;i ++ )scanf( "%d", &p[i].u );for( int i = 1;i <= n;i ++ )scanf( "%d", &p[i].s );sort( p + 1, p + n + 1, cmp );for( int i = 1;i <= n;i ++ )G[p[i].u].push_back( p[i].s );for( int i = 1;i <= n;i ++ ) {int siz = G[i].size();for( int j = siz - 1;j > 0;j -- )G[i][j - 1] += G[i][j];for( int j = 1;j <= siz;j ++ )ans[j] += G[i][siz % j];}for( int i = 1;i <= n;i ++ )printf( "%lld ", ans[i] );printf( "\n" );}return 0;
}

D. Maximum Sum of Products

分奇偶区间做,双指针进行交换相乘,再预处理O(1)O(1)O(1)查未翻转的区间值

时间复杂度O(n2)O(n^2)O(n2)

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 5005
int n;
int a[maxn], b[maxn];signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &a[i] );for( int i = 1;i <= n;i ++ )scanf( "%lld", &b[i] );int ans = 0, x;for( int i = 1;i <= n;i ++ )ans += a[i] * b[i];x = ans;for( int i = 1;i <= n;i ++ ) {int l = i - 1, r = i + 1, t = x;while( l && r <= n ) {t -= ( a[l] * b[l] + a[r] * b[r] );t += ( a[l] * b[r] + a[r] * b[l] );ans = max( ans, t );l --, r ++;}}for( int i = 1;i < n;i ++ ) {int l = i, r = i + 1, t = x;while( l && r <= n ) {t -= ( a[l] * b[l] + a[r] * b[r] );t += ( a[l] * b[r] + a[r] * b[l] );ans = max( ans, t );l --, r ++;}}printf( "%lld\n", ans );return 0;
}

E. Off by One

两个新点的斜率要穿过原点

本质就是各个新点与(0,0)(0,0)(0,0)直线的斜率相同配对

建一个奇妙的图

连边iii点可以变的两个点各自的斜率,每条边则代表原来的一个点

问题转化为在该图上找共用一个端点的临边对,且每个边只能被选一次的最大配对数量

思路采取dfsdfsdfs搜树,如果儿子没有与其子孙配对成功,再考虑跟兄弟配对,这样一定是最优策略

#include <iostream>
#include <vector>
#include <cstdio>
#include <map>
using namespace std;
#define maxn 400005
#define int long long
#define Pair pair < int, int >
map < Pair, int > id;
vector < Pair > G[maxn], ans;
int n, cnt;
int vis[maxn];int dfs( int u ) {vis[u] = 1;int last = -1;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first, now = G[u][i].second;if( vis[v] == 1 ) continue;if( ! vis[v] ) {int t = dfs( v );if( ~ t ) {ans.push_back( make_pair( now, t ) );now = -1;} else;} else;if( ~ now ) {if( ~ last ) {ans.push_back( make_pair( last, now ) );last = -1;} else last = now;} else;}vis[u] = 2;return last;
}int gcd( int x, int y ) {if( x < y ) swap( x, y );if( ! y ) return x;else return gcd( y, x % y );
}Pair work( int x, int y ) {int d = gcd( x, y );return make_pair( x / d, y / d );
}signed main() {scanf( "%lld", &n );for( int i = 1, a, b, c, d, D;i <= n;i ++ ) {scanf( "%lld %lld %lld %lld", &a, &b, &c, &d );Pair k1 = work( ( a + b ) * d, c * b );Pair k2 = work( a * d, ( c + d ) * b );if( ! id.count( k1 ) ) id[k1] = ++ cnt; else;if( ! id.count( k2 ) ) id[k2] = ++ cnt; else;G[id[k1]].push_back( make_pair( id[k2], i ) );G[id[k2]].push_back( make_pair( id[k1], i ) );}for( int i = 1;i <= cnt;i ++ )if( ! vis[i] ) dfs( i );printf( "%lld\n", ( int )ans.size() );for( int i = 0;i < ans.size();i ++ )printf( "%lld %lld\n", ans[i].first, ans[i].second );return 0;
}

Codeforces Global Round 14——1515

A. Phoenix and Gold

直接队列操作,中途恰好为xxx时先跳过,后面再使用

设置一个次数,次数截止时还没有退出,就是无解

#include <cstdio>
#include <queue>
using namespace std;
#define maxn 105
int T, n, x;
queue < int > q;
int ans[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d", &n, &x );while( ! q.empty() ) q.pop();for( int i = 1, w;i <= n;i ++ )scanf( "%d", &w ), q.push( w );int sum = 0, t = n << 1, cnt = 0;while( ! q.empty() && t -- ) {int w = q.front(); q.pop();if( sum + w != x ) ans[++ cnt] = w, sum += w;else q.push( w );}if( ! q.empty() ) printf( "NO\n" );else {printf( "YES\n" );for( int i = 1;i <= cnt;i ++ )printf( "%d ", ans[i] );printf( "\n" );}}return 0;
}

B. Phoenix and Puzzle

结论:个数一定是2x/4x,x=k2,k∈Z2x/4x,x=k^2,k∈Z2x/4x,x=k2,kZ 显然

最基本的正方形要么是两个拼,要么是四个拼

再用基本正方形拼成大的正方形,边长要是一定,面积就是个平方数了

#include <cstdio>
#include <cmath>
using namespace std;
int T, n;int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );if( n % 2 == 0 ) {int sqr = sqrt( n >> 1 );if( sqr * sqr == ( n >> 1 ) ) {printf( "YES\n" );continue;} else;} else;if( n % 4 == 0 ) {int sqr = sqrt( n >> 2 );if( sqr * sqr == ( n >> 2 ) ) {printf( "YES\n" );continue;} else;}printf( "NO\n" );}return 0;
}

C. Phoenix and Towers

贪心地想

按高度排序后,划分一定是折现形,最后不足mmm个的部分,用个优先队列,倒着填入最小高度盒

不难证明,这是尽量缩小每组的差距的最优策略

#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100005
struct node {int h, id;node() {}node( int H, int ID ) {h = H, id = ID;}bool operator < ( node t ) const {return h > t.h;}
}block[maxn];
int ans[maxn], h[maxn];
priority_queue < node > q;
int T, n, m, x;bool cmp( node s, node t ) {return s.h < t.h;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d %d", &n, &m, &x );for( int i = 1;i <= m;i ++ ) h[i] = 0;while( ! q.empty() ) q.pop();for( int i = 1;i <= n;i ++ )scanf( "%d", &block[i].h ), block[i].id = i;sort( block + 1, block + n + 1, cmp );for( int t = 1;t <= n / m;t ++ ) {int pos = ( t - 1 ) * m + 1;if( t & 1 ) {for( int ip = 1;ip <= m;ip ++, pos ++ )ans[block[pos].id] = ip, h[ip] += block[pos].h;} else {for( int ip = m;ip;ip --, pos ++ )ans[block[pos].id] = ip, h[ip] += block[pos].h;}}for( int i = 1;i <= m;i ++ )q.push( node( h[i], i ) );int t = n % m, pos = n;while( t -- ) {int i = q.top().id;q.pop();ans[block[pos].id] = i;h[i] += block[pos].h;pos --;q.push( node( h[i], i ) );}sort( h + 1, h + m + 1 );if( h[m] - h[1] > x ) printf( "NO\n" );else {printf( "YES\n" );for( int i = 1;i <= n;i ++ )printf( "%d ", ans[i] );printf( "\n" );}}return 0;
}

D. Phoenix and Socks

贪心分类讨论

将袜子按颜色分类统计个数,左袜子−1-11,右袜子+1+1+1,配对花费为000

只花费111的操作,不可能有花费222的操作

也就是尽量避免既改变朝向又改变颜色

  • 改变袜子颜色

    将袜子个数为奇数的颜色按剩的左右袜子扔进队列L,RL,RL,R

    L,RL,RL,R两两配对完后

    L,RL,RL,R中最多只有一个队列还有奇数袜子的颜色,与另外方向的偶数袜子颜色进行配对,一次性配对两个

  • 改变袜子左右

    经过上面的操作,每个颜色都是偶数的袜子还没配对,内部消化即可

#include <cstdio>
#include <queue>
using namespace std;
#define maxn 200005
queue < int > L, R;
int n, l, r, T;
int c[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d %d", &n, &l, &r );for( int i = 1;i <= n;i ++ ) c[i] = 0;for( int i = 1, color;i <= n;i ++ ) {scanf( "%d", &color );if( i <= l ) c[color] --;else c[color] ++;}for( int i = 1;i <= n;i ++ )if( c[i] < 0 )if( c[i] & 1 ) L.push( i ); else;elseif( c[i] & 1 ) R.push( i ); else;int ans = 0;while( ! L.empty() && ! R.empty() ) {ans ++;c[L.front()] ++, L.pop();c[R.front()] --, R.pop();}if( ! L.empty() ) {for( int i = 1;i <= n;i ++ ) {if( c[i] <= 0 ) continue;else {while( c[i] && ! L.empty() ) {c[i] -= 2, ans += 2;c[L.front()] ++, L.pop();c[L.front()] ++, L.pop();if( L.empty() ) break;}}if( L.empty() ) break;}if( ! L.empty() ) {while( ! L.empty() ) {c[L.front()] ++, L.pop();c[L.front()] ++, L.pop();ans += 2;}}}if( ! R.empty() ) {for( int i = 1;i <= n;i ++ ) {if( c[i] >= 0 ) continue;else {while( c[i] && ! R.empty() ) {c[i] += 2, ans += 2;c[R.front()] --, R.pop();c[R.front()] --, R.pop();}}if( R.empty() ) break;}if( ! R.empty() ) {while( ! R.empty() ) {ans += 2;c[R.front()] --, R.pop();c[R.front()] --, R.pop();}}}for( int i = 1;i <= n;i ++ ) {if( c[i] < 0 ) c[i] = -c[i];ans += ( c[i] >> 1 );}printf( "%d\n", ans );}return 0;
}

E. Phoenix and Computers

插入DPDPDP/连续段DPDPDP

#include <cstdio>
#define maxn 405
#define int long long
int f[maxn][maxn];
int n, mod;
//f[i][j]已经亮了i个灯泡 构成j个连续段 方案数 
signed main() {scanf( "%lld %lld", &n, &mod );f[0][0] = 1;for( int i = 0;i < n;i ++ )for( int j = 0;j <= i;j ++ ) {f[i + 1][j + 1] = ( f[i + 1][j + 1] + f[i][j] * ( j + 1 ) % mod ) % mod;//j段 j+1个间隔 随便插入其中一个间隔 产生新的连续段 f[i + 1][j] = ( f[i + 1][j] + f[i][j] * j * 2 % mod ) % mod;//贴着j段左右两种情况开灯 连续段个数不变 f[i + 2][j] = ( f[i + 2][j] + f[i][j] * j * 2 % mod ) % mod;//与j段间隔2开灯中间夹着的灯自动打开 连续段个数不变 if( j >= 2 ) {f[i + 2][j - 1] = ( f[i + 2][j - 1] + f[i][j] * ( j - 1 ) * 2 ) % mod;//j段每两段一个间隔共j-1个间隔 两段距离为2随便选一个便连接起了两段 f[i + 3][j - 1] = ( f[i + 3][j - 1] + f[i][j] * ( j - 1 ) ) % mod;//距离为3 插正中间的灯泡  }}printf( "%lld\n", f[n][1] );return 0;
}

F. Phoenix and Earthquake

如果沥青总数不够n−1n-1n1条道路要扣除的xxx,就一定无解;反之一定有解

故此随便搞一棵生成树,dfsdfsdfs搜树

如果能与儿子修路就直接修,否则就将这条路暂定

最后都汇聚到起点后,从起点开始像吸面条一样往下修路

#include <stack>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define maxn 300005
stack < int > q;
int n, m, X;
vector < pair < int, int > > G[maxn];
int f[maxn], w[maxn], a[maxn];int find( int x ) {return x == f[x] ? f[x] : f[x] = find( f[x] );
}bool Union( int u, int v ) {int fu = find( u ), fv = find( v );if( w[fu] + w[fv] < X ) return 0;f[fv] = fu, w[fu] += w[fv] - X;return 1;
}void dfs( int u, int fa, int id ) {for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;if( v == fa ) continue;dfs( v, u, G[u][i].second );}if( find( fa ) == find( u ) ) return;if( Union( fa, u ) ) printf( "%lld\n", id );else q.push( id );
}signed main() {int sum = 0;scanf( "%lld %lld %lld", &n, &m, &X );for( int i = 1;i <= n;i ++ ) {scanf( "%lld", &a[i] );sum += a[i];}for( int i = 1;i <= n;i ++ ) f[i] = i;for( int i = 1, u, v;i <= m;i ++ ) {scanf( "%lld %lld", &u, &v );int fu = find( u ), fv = find( v );if( fu == fv ) continue;f[fv] = fu;G[u].push_back( make_pair( v, i ) );G[v].push_back( make_pair( u, i ) );}if( sum < X * ( n - 1 ) ) return ! printf( "NO\n" );for( int i = 1;i <= n;i ++ ) f[i] = i, w[i] = a[i];printf( "YES\n" );dfs( 1, 1, 0 );while( ! q.empty() ) printf( "%lld\n", q.top() ), q.pop();return 0;
}

G. Phoenix and Odometers

题目问题的特殊性:有向图中的环转化为强联通图

也就是说强联通彼此应该是独立求解的(有向图要走出去又走回来,不是强联通根本不可能)

所以接下来就假设是对强联通图进行求解,在模意义(以下省略不写)下发现距离性质:

  • u→vu\rightarrow vuv存在一个长度为lenlenlen的路径,则v→uv\rightarrow uvu一定存在一条长度为−len-lenlen的路径

    v→uv\rightarrow uvu距离为xxx,则可以选择从vvvmod−1mod-1mod1xxxlenlenlen,最后还是停在vvv,再走一次xxx,到达uuu

    则有:mod∗x+(mod−1)∗len≡−lenmod*x+(mod-1)*len\equiv -lenmodx+(mod1)lenlen

  • u→uu\rightarrow uuu存在一条长度为lenlenlen的路径,则u→uu\rightarrow uuu一定存在一条长度为k∗lenk*lenklen的路径

    路径其实就是一条环长,将环走kkk遍即可

  • u→uu\rightarrow uuu一定存在一条长度为000的路径

    将环走恰好modmodmod遍,模意义下的长度就是000

  • 如果u→uu\rightarrow uuu存在长度为x,yx,yx,y的路径,那么一定存在长度为x+y,x−yx+y,x-yx+y,xy的路径

    x+yx+yx+y无非就是走一遍长度为xxx的环,再走一遍长度为yyy的环

    x−yx-yxy需要用到第一条性质:u→uu\rightarrow uuu存在长度为yyy的路径,则u→uu\rightarrow uuu也存在长度为−y-yy的环

    在该条性质上,进一步拓展得到:一定存在长度为ax+byax+byax+by的环

对于每个点,其环长集为SSS,满足∀x,yax+by∈S(a,b∈Z)\forall_{x,y}\ ax+by∈S\ (a,b∈Z)x,y ax+byS (a,bZ)

由扩展欧几里得知道,一个数能被ax+byax+byax+by凑出来,当且仅当该数为gcd(x,y)gcd(x,y)gcd(x,y)的倍数

所以每个点的环长集SSS就等同于一个数ggg的倍数集,ggg为所有环长的gcdgcdgcd

枚举所有环长的gcdgcdgcd,询问成立当且仅当s+gs+gs+g的倍数是ttt的倍数,转换成sssgcd(g,t)gcd(g,t)gcd(g,t)的倍数

#include <stack>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 200005
#define int long long
stack < int > st;
vector < pair < int, int > > G[maxn];
int dfn[maxn], low[maxn], scc[maxn], d[maxn], g[maxn];
bool vis[maxn];
int n, m, Q, cnt, tot;void tarjan( int u ) {dfn[u] = low[u] = ++ cnt, st.push( u );for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;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 = st.top(), st.pop(), scc[v] = tot;}while( v != u );}
}void dfs( int u ) {vis[u] = 1;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first, w = G[u][i].second;if( scc[v] != scc[u] || vis[v] ) continue;else d[v] = d[u] + w, dfs( v );}	
}int gcd( int x, int y ) {if( x < y ) swap( x, y );if( ! y ) return x;else return gcd( y, x % y );
}signed main() {scanf( "%lld %lld", &n, &m );for( int i = 1, u, v, w;i <= m;i ++ ) {scanf( "%lld %lld %lld", &u, &v, &w );G[u].push_back( make_pair( v, w ) );}for( int i = 1;i <= n;i ++ ) if( ! dfn[i] ) tarjan( i );for( int i = 1;i <= n;i ++ ) if( ! vis[i] ) dfs( i );for( int i = 1;i <= n;i ++ )for( int j = 0;j < G[i].size();j ++ ) {int k = G[i][j].first, w = G[i][j].second;if( scc[k] != scc[i] ) continue;int len = d[i] + w - d[k];if( len < 0 ) len = -len;g[scc[i]] = gcd( g[scc[i]], len );}scanf( "%lld", &Q );while( Q -- ) {int v, s, t;scanf( "%lld %lld %lld", &v, &s, &t );if( s % gcd( g[scc[v]], t ) == 0 ) printf( "YES\n" );else printf( "NO\n" );}return 0;
}

#720 (Div. 2)——1521

A. Nastia and Nearly Good Numbers

直接A,A∗(B−1),A∗BA,A*(B-1),A*BA,A(B1),AB即可,如果B=2B=2B=2,后两个翻倍即可,一定要特判B=1,NOB=1,NOB=1,NO

B. Nastia and a Good Array

做法很多,仅分享自己的做法

既然不要求最小化操作次数,不用白不用

线性从左到右扫一遍,如果不互质,分情况讨论

i+1i+1i+1对应更大,直接更改为iii左边较小数+1+1+1

iii左边更大,如果右边i+1i+1i+1i−1i-1i1对应数值直接互换;否则就暴力增加iii使之与左右均互质

不难知道,增加量非常小

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
pair < pair < int, int >, pair < int, int > > ans[maxn];
int A[maxn];
int T, n;int gcd( int x, int y ) {if( x < y ) swap( x, y );if( ! y ) return x;else return gcd( y, x % y );
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &A[i] );int cnt = 0;for( int i = 1;i < n;i ++ ) {if( gcd( A[i], A[i + 1] ) == 1 ) continue;else {if( A[i] <= A[i + 1] ) {ans[++ cnt] = make_pair( make_pair( i, i + 1 ), make_pair( A[i], A[i] + 1 ) );A[i + 1] = A[i] + 1;}else {if( gcd( A[i - 1], A[i + 1] ) == 1 ) {ans[++ cnt] = make_pair( make_pair( i, i + 1 ), make_pair( A[i + 1], A[i + 1] + 1 ) );A[i] = A[i + 1], A[i + 1] ++;}else {while( ( i > 1 && gcd( A[i], A[i - 1] ) != 1 ) || gcd( A[i], A[i + 1] ) != 1 )A[i] ++;ans[++ cnt] = make_pair( make_pair( i, i + 1 ), make_pair( A[i], A[i + 1] ) );}}}}printf( "%d\n", cnt );for( int i = 1;i <= cnt;i ++ )printf( "%d %d %d %d\n", ans[i].first.first, ans[i].first.second, ans[i].second.first, ans[i].second.second );}return 0;
}

C. Nastia and a Hidden Permutation

方法很多

最小值的最大值,最大值的最小值,其实就是抓住n−1/1n-1/1n1/1去判断

不妨先想办法找到最大值,然后一个一个与最大值调用t=2t=2t=2就可以求出其值

也就是说在n2\frac{n}{2}2n左右求出最大值位置

两两分组考虑,利用t=1,max(min(n−1,ai),min(n,ai+1))t=1,max(min(n-1,a_i),min(n,a_{i+1}))t=1,max(min(n1,ai),min(n,ai+1))

如果返回值为nnn,那么直接锁定i+1i+1i+1

如果返回值为n−1n-1n1,既可能恰好ai=n,ai+1=n−1a_i=n,a_{i+1}=n-1ai=n,ai+1=n1,也有可能ai=n−1,ai+1<n−1a_i=n-1,a_{i+1}<n-1ai=n1,ai+1<n1

这个时候就再反着询问一遍,max(min(n−1,ai+1),min(n,ai))max(min(n-1,a_{i+1}),min(n,a_i))max(min(n1,ai+1),min(n,ai)),如果是nnn就锁定了iii,否则跳到下一组

#include <cstdio>
#define maxn 10005
int T, n;
int p[maxn];int print( int t, int i, int j, int x ) {printf( "? %d %d %d %d\n", t, i, j, x );fflush( stdout );int val;scanf( "%d", &val );return val;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )p[i] = 0;int pos = 0;for( int i = 1;i < n;i += 2 ) {int val = print( 1, i, i + 1, n - 1 );if( val >= n - 1 ) {if( val == n ) { pos = i + 1; break; }val = print( 1, i + 1, i, n - 1 );if( val == n ) { pos = i; break; }}}if( ! pos ) pos = n;for( int i = 1;i <= n;i ++ )if( pos == i ) {p[i] = n;continue;}elsep[i] = print( 2, i, pos, 1 );printf( "!" );for( int i = 1;i <= n;i ++ )printf( " %d", p[i] );printf( "\n" );fflush( stdout );}return 0;
}

D. Nastia Plays with a Tree

删掉若干条边,新添若干条边,使得最后图为一条链的最小操作数

对于一个点,其最多保留两条边,删边数似乎是确定的,就是最小操作数;接下来是想办法连边构造出链

定义规则,如果某点有大于等于两个儿子,就断掉与父亲的边,并且随便留下两个儿子与自己还连着边

然后就把若干条链串起来即可(可参考代码)

#include <cstdio>
#include <vector>
using namespace std;
#define maxn 100005
struct node {int v1, v2, v3, v4;node(){}node( int V1, int V2, int V3, int V4 ) {v1 = V1, v2 = V2, v3 = V3, v4 = V4;}
};
vector < node > ans;
vector < int > G[maxn];
int T, n;int dfs( int u, int fa ) {int pos = u, cnt = 0;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;int now = dfs( v, u );//返回的点一定是儿子节点数<=1的(不包含父亲这个节点) if( now < 0 ) continue;//儿子v已经完成了(u,v)的断边 实际上u与这个儿子已经无瓜 cnt ++;if( cnt == 1 ) pos = now;//第一个儿子成为该可能链负责链出去的节点 else if( cnt == 2 ) ans.push_back( node( u, fa, now, pos ) ), pos = -1;//断掉与fa的边 选择保留两个儿子连边 第一个儿子负责链出上一个链 第二个儿子负责链入下一个链 else ans.push_back( node( u, v, now, v ) );//如果v自己没有与u断边 说明v只有一个儿子 }return pos;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )G[i].clear();ans.clear();for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}int rt = 1;while( rt <= n && G[rt].size() != 1 ) rt ++; int head = dfs( rt, -1 );//保证了rt一定是一条链的头 这样才能一条条链起来 不会出现环 printf( "%d\n", ans.size() );for( int i = 0;i < ans.size();i ++ )printf( "%d %d %d %d\n", ans[i].v1, ans[i].v2, ans[i].v3, head ), head = ans[i].v4;}return 0;
}

E. Nastia and a Beautiful Matrix

img

对于一个n×nn\times nn×n的矩阵,最多能填的个数为奇数列+偶数列的奇数行,⌈n2⌉×n+(n−⌈n2⌉)×⌈n2⌉\lceil\frac{n}{2}\rceil\times n+(n-\lceil\frac{n}{2}\rceil)\times \lceil\frac{n}{2}\rceil2n×n+(n2n)×2n

显然蓝色格子可以乱填,但是红色与黄色可能出现冲突,对于一种出现次数最多的颜色最多就是把奇数列占满

所以需要满足cnt=max{ai},cnt≤⌈n2⌉×ncnt=max\{a_i\},cnt\le \lceil\frac{n}{2}\rceil\times ncnt=max{ai},cnt2n×n

这肯定具有单调性,二分查找满足条件的最小nnn,最后就只剩构造了

按个数从大到小填,先填红色,再填蓝色,最后填黄色,就肯定不可能出现红色黄色冲突

如果红色最后几个填了次大,次次大……也肯定不会红黄冲突的,否则那些颜色才是出现次数最多的颜色

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100005
vector < pair < int, int > > G, R, B, Y;
int T, m, k, cnt, maxx;
int mp[1005][1005];int find() {int l = 1, r = m, ans;while( l <= r ) {int mid = ( l + r ) >> 1;if( cnt <= 2 * ( ( mid + 1 ) / 2 ) * mid - ( ( mid + 1 ) / 2 ) * ( ( mid + 1 ) / 2 ) && maxx <= mid * ( ( mid + 1 ) / 2 ) )ans = mid, r = mid - 1;elsel = mid + 1;}return ans;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d", &m, &k );cnt = maxx = 0;G.clear(), R.clear(), B.clear(), Y.clear();for( int i = 1, x;i <= k;i ++ ) {scanf( "%d", &x );G.push_back( make_pair( x, i ) );cnt += x, maxx = max( maxx, x );}sort( G.begin(), G.end() );int n = find();printf( "%d\n", n );for( int i = 1;i <= n;i ++ )for( int j = 1;j <= n;j ++ )mp[i][j] = 0;for( int i = 1;i <= n;i += 2 )for( int j = 1;j <= n;j += 2 )B.push_back( make_pair( i, j ) );for( int i = 2;i <= n;i += 2 )for( int j = 1;j <= n;j += 2 )R.push_back( make_pair( i, j ) );for( int i = 1;i <= n;i += 2 )for( int j = 2;j <= n;j += 2 )Y.push_back( make_pair( i, j ) );for( int i = G.size() - 1;~ i;i -- )while( G[i].first ) {G[i].first --;if( ! R.empty() ) mp[R.back().first][R.back().second] = G[i].second, R.pop_back();else if( ! B.empty() ) mp[B.back().first][B.back().second] = G[i].second, B.pop_back();else mp[Y.back().first][Y.back().second] = G[i].second, Y.pop_back();}for( int i = 1;i <= n;i ++ ) {for( int j = 1;j <= n;j ++ )printf( "%d ", mp[i][j] );printf( "\n" );}}return 0;
} 

Codeforces Global Round 13——1491

A. K-th Largest Value

统计111的个数即可,每次改变±1±1±1

B. Minimal Cost

∀i>1,ai=ai−1ans=min(ans,v+min(u,v))\forall_{i>1,a_i=a_{i-1}}\ ans=min(ans,v+min(u,v))i>1,ai=ai1 ans=min(ans,v+min(u,v))

∀i>1,ai=ai−1+1ans=min(ans,min(u,v))\forall_{i>1,a_i=a_{i-1}+1}\ ans=min(ans,min(u,v))i>1,ai=ai1+1 ans=min(ans,min(u,v))

∀i>1,ai>ai−1+1ans=0\forall_{i>1,a_i>a_{i-1}+1}\ ans=0i>1,ai>ai1+1 ans=0

C. Pekora and Trampoline

对于任何一个Si≠1S_i≠1Si=1iii而言,由于每次跳SiS_iSi都会恰好减小111,所以iii直接到达的点为[i+2,i+Si][i+2,i+S_i][i+2,i+Si]

(ps:i+1i+1i+1Si=1S_i=1Si=1的特殊情况归为第二类)

之后再每次从iii跳,直接到达的点只有i+1i+1i+1

不妨对于每个点记录一下被前面点到达的次数,再与SiS_iSi比较,多的次数全都往i+1i+1i+1贡献

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 5005
int T, n;
int s[maxn], cnt[maxn];signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &s[i] ), cnt[i] = 0;int ans = 0;for( int i = 1;i <= n;i ++ ) {ans += max( s[i] - cnt[i] - 1, 0ll );for( int j = i + 2;j <= min( i + s[i], n );j ++ )cnt[j] ++;cnt[i + 1] += max( cnt[i] - s[i] + 1, 0ll );}printf( "%lld\n", ans );}return 0;
}

D. Zookeeper and The Infinite Zoo

&操作转换到二进制下进行,则每次操作可以看成是001...111 & 1→\rightarrow010...000

每次都是将二进制上111的数往左推的过程

意味着每一位的前缀和111的个数uuu都要大于等于vvv

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 40
int Q, u, v;
int num1[maxn], num2[maxn];int main() {scanf( "%d", &Q );while( Q -- ) {scanf( "%d %d", &u, &v );if( u > v ) {printf( "NO\n" );continue;}int cnt1 = 0, cnt2 = 0;while( u ) {num1[++ cnt1] = u & 1;u >>= 1;}while( v ) {num2[++ cnt2] = v & 1;v >>= 1;}int n = max( cnt1, cnt2 );for( int i = 1;i <= n;i ++ )num1[i] += num1[i - 1];for( int i = 1;i <= n;i ++ )num2[i] += num2[i - 1];bool flag = 0;for( int i = 1;i <= n;i ++ )if( num1[i] < num2[i] ) {flag = 1;break;}if( flag ) printf( "NO\n" );else printf( "YES\n" );memset( num1, 0, sizeof( num1 ) );memset( num2, 0, sizeof( num2 ) );}return 0;
}

E. Fib-tree

  • 一个大小为FnF_nFn的树,只能切割成大小为Fn−1,Fn−2F_{n-1},F_{n-2}Fn1,Fn2的子树

    求证:Fi=Fj+Fk,j>kF_i=F_j+F_k,j>kFi=Fj+Fk,j>k的唯一解为(i−1,i−2)(i-1,i-2)(i1,i2)

    显然,i>j>k,Fi≥Fi−1≥Fi−2⇒Fi≥Fi−2∗2i>j>k,F_i\ge F_{i-1}\ge F_{i-2}\Rightarrow F_{i}\ge F_{i-2} * 2i>j>k,FiFi1Fi2FiFi22

    当且仅当i=2i=2i=2时取等(i=2i=2i=2时,F1=F0F_1=F_0F1=F0一样满足结论)

  • 切割边的顺序不影响最终结果

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 200005
vector < pair < int, int > > G[maxn];
vector < int > fib;
int n;
int siz[maxn];void dfs1( int u, int fa ) {siz[u] = 1;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;if( v == fa || G[u][i].second ) continue;else dfs1( v, u ), siz[u] += siz[v];}
}void dfs2( int now, int fa, int k, int &u, int &v, int &t ) {for( int i = 0;i < G[now].size();i ++ ) {if( u ) return;int nxt = G[now][i].first;if( nxt == fa || G[now][i].second ) continue; if( siz[nxt] == fib[k - 1] || siz[nxt] == fib[k - 2] ) {u = now, v = nxt;t = siz[nxt] == fib[k - 1] ? k - 1 : k - 2;}dfs2( nxt, now, k, u, v, t );}
}void solve( int x, int k ) {if( k <= 1 ) return;dfs1( x, 0 );int u = 0, v = 0, t = 0;dfs2( x, 0, k, u, v, t );if( ! u ) {printf( "NO\n" );exit( 0 );}for( int i = 0;i < G[u].size();i ++ )if( G[u][i].first == v ) G[u][i].second = 1;for( int i = 0;i < G[v].size();i ++ )if( G[v][i].first == u ) G[v][i].second = 1;solve( v, t );solve( u, t == k - 1 ? k - 2 : k - 1 );
}int main() {scanf( "%d", &n );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( make_pair( v, 0 ) );G[v].push_back( make_pair( u, 0 ) );}fib.push_back( 1 );fib.push_back( 1 );for( int i = 1;;i ++ ) {if( fib[i] >= n ) break;fib.push_back( fib[i] + fib[i - 1] );}if( fib[fib.size() - 1] != n ) return ! printf( "NO\n" );solve( 1, fib.size() - 1 );printf( "YES\n" );return 0;
}

F. Magnets

把第一个磁铁放在左边,下一个放在右边

如果机器返回000,说明左右两边至少有一个是无磁性的,把放在右边的放到左边,再把下一个放右边比较

相当于,对于磁铁i=2,3...ni=2,3...ni=2,3...n,依次询问[1,i−1][1,i-1][1,i1]与磁铁iii的力大小

在第一次返回力大小不为零时停止

此时右边孤零零的磁铁一定是有磁性的,而左边则只有一个有磁性,其余全是无磁性

对于右边iii后面的所有磁铁,与iii一一询问

二分[1,i−1][1,i-1][1,i1]中有磁性的位置

上限是n−1+⌈log2n⌉n-1+\lceil{log_2^n\rceil}n1+log2n

#include <cstdio>
#include <vector>
using namespace std;
#define maxn 2005
vector < int > ans;
int T, n, magnet;void print( int l1, int r1, int l2, int r2 ) {printf( "? %d %d\n", r1 - l1 + 1, r2 - l2 + 1 );for( int i = l1;i <= r1;i ++ )printf( "%d ", i );printf( "\n" );for( int i = l2;i <= r2;i ++ )printf( "%d ", i );printf( "\n" );fflush( stdout );scanf( "%d", &magnet );
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );int idx;for( int i = 2;i <= n;i ++ ) {print( 1, i - 1, i, i );if( magnet ) {idx = i;break;} else;}ans.clear();int l = 1, r = idx - 1, pos;while( l <= r ) {int mid = ( l + r ) >> 1;print( 1, mid, idx, idx );if( magnet ) pos = mid, r = mid - 1;else l = mid + 1;}for( int i = 1;i < idx;i ++ )if( i != pos ) ans.push_back( i );for( int i = idx + 1;i <= n;i ++ ) {print( idx, idx, i, i );if( ! magnet ) ans.push_back( i );}printf( "! %d", ans.size() );for( int i = 0;i < ans.size();i ++ )printf( " %d", ans[i] );printf( "\n" );fflush( stdout );}return 0;
}

Educational Codeforces Round 109 (Rated for Div. 2)——1525

A. Potion-making

一个gcdgcdgcd完事

B. Permutation Sort

暴力大讨论

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 55
int T, n;
int a[maxn];
bool vis[maxn];int main() {scanf( "%d", &T );again :while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &a[i] );if( a[1] == n && a[n] == 1 ) {printf( "3\n" );goto again;}bool flag = 1;for( int i = 1;i <= n;i ++ )if( a[i] > a[i - 1] ) continue;else flag = 0;if( flag ) {printf( "0\n" );goto again;}for( int i = 1;i <= n;i ++ ) {for( int j = 1;j <= i;j ++ )if( a[j] > a[j - 1] ) continue;else flag = 0;if( flag ) {printf( "1\n" );goto again;}for( int j = 1;j <= i;j ++ )vis[a[j]] = 1;for( int j = 1;j <= i;j ++ )if( ! vis[j] ) flag = 0;if( flag ) {printf( "2\n" );goto again;}}for( int l = 1;l < n;l ++ )for( int r = l + 1;r <= n;r ++ )if( l == 1 && r == n ) continue;else {flag = 1;memset( vis, 0, sizeof( vis ) );for( int i = l;i <= r;i ++ )vis[a[i]] = 1;for( int i = 1;i < l;i ++ )if( a[i] < a[i - 1] ) flag = 0; if( !  flag ) continue;for( int i = r + 1;i <= n;i ++ )if( a[i] < a[i - 1] ) flag = 0;if( ! flag ) continue;for( int i = l;i <= r;i ++ )if( ! vis[i] ) flag = 0;if( ! flag ) continue;printf( "1\n" );goto again;}printf( "2\n" );}return 0;
}

C. Robot Collisions

奇偶坐标肯定是互相不可能撞到的,然后就是左右互撞,或者左左/右右撞墙再互撞

先按坐标排序,把方向为右的都加进栈,遇到方向为左的就与栈顶互撞;没有就自己进栈

同向撞,相当于先进栈的撞了一次墙,可以看成从离墙−x-xx的距离相向运动

#include <stack>
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 300005
struct node {int x, dir, id;
}robot[maxn];
int T, n, m;
stack < node > s[2];
int ans[maxn];bool cmp( node s, node t ) {return s.x < t.x;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ )scanf( "%d", &robot[i].x ), robot[i].id = i, ans[i] = -1;for( int i = 1;i <= n;i ++ ) {char ch;scanf( " %c", &ch );robot[i].dir = ( ch == 'L' ? -1 : 1 );}sort( robot + 1, robot + n + 1, cmp );while( ! s[0].empty() ) s[0].pop();while( ! s[1].empty() ) s[1].pop();for( int i = 1;i <= n;i ++ ) {int k = robot[i].x & 1;if( robot[i].dir == -1 ) {if( ! s[k].empty() ) {node lst = s[k].top(); s[k].pop();ans[lst.id] = ans[robot[i].id] = ( robot[i].x - ( lst.dir == 1 ? lst.x : -lst.x ) ) / 2;}else s[k].push( robot[i] );}else s[k].push( robot[i] );}for( int k = 0;k < 2;k ++ ) {while( s[k].size() > 1 ) {node i = s[k].top(); s[k].pop();node j = s[k].top(); s[k].pop();ans[i.id] = ans[j.id] = ( 2 * m - i.x - ( j.dir == 1 ? j.x : -j.x ) ) / 2;}}for( int i = 1;i <= n;i ++ )printf( "%d ", ans[i] );printf( "\n" );}return 0;
}

D. Armchairs

dpi,jdp_{i,j}dpi,jiii个初始被占的位置使用了前jjj个空位

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 5005
int n, ans, cnt1, cnt2;
int a[maxn], p1[maxn], p2[maxn];
int dp[maxn][maxn];int Fabs( int x ) {return x < 0 ? -x : x;
}int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%d", &a[i] );if( a[i] ) p1[++ cnt1] = i;else p2[++ cnt2] = i;}if( ! cnt1 ) return ! printf( "0\n" );memset( dp, 0x3f, sizeof( dp ) );for( int i = 0;i <= cnt2;i ++ )dp[0][i] = 0;for( int i = 1;i <= cnt1;i ++ )for( int j = 1;j <= cnt2;j ++ )dp[i][j] = min( dp[i][j - 1], dp[i - 1][j - 1] + Fabs( p1[i] - p2[j] ) );printf( "%d\n", dp[cnt1][cnt2] );return 0;
}

#721 (Div. 2)——1527

A. And Then There Were K

nnn用二进制表示,不难发现只有nnn111的最高位,kkk000即可

因为[k,n][k,n][k,n]中的每个数总有不是最高位的其它为111位置的为000,与nnn并一下就变成000

B. Palindrome Game

简单情况保证原串为一个回文串

  • 零的个数为000

    什么也不用干,DRAWDRAWDRAW

  • 零的个数为111

    AAA无法使用反转操作,支付代价,BOBBOBBOB

  • 零的个数为奇数

    ALICEALICEALICE拥有必胜策略

    先花费111占据最中间的000,然后BOBBOBBOB每次不管操作哪里的零,ALICEALICEALICE都执行对称操作

    最后一次BOBBOBBOB操作后,还剩下一个零,两人花费相同;ALICEALICEALICE反转强制BOBBOBBOB多花费111

  • 零的个数为偶数

    BOBBOBBOB拥有必胜策略,类比ALICEALICEALICE策略

    一开始ALICEALICEALICE不能反转,只能花费111BOBBOBBOB执行对称花费

    最后一次ALICEALICEALICE花费,BOBBOBBOB反转强制ALICEALICEALICE再次花费,最后ALICEALICEALICE多花费222

#include <cstdio>
#define maxn 1005
int T, n;
char s[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %s", &n, s + 1 );int cnt = 0;for( int i = 1;i <= n;i ++ )cnt += ( s[i] == '0' );if( cnt == 0 ) printf( "DRAW\n" );else if( cnt == 1 ) printf( "BOB\n" );else if( cnt & 1 ) printf( "ALICE\n" );else printf( "BOB\n" );}return 0;
}
  • 原串为回文串

    参照easy version

  • 原串不为回文串

    结果一定是ALICEALICEALICE胜或者DRAWDRAWDRAW

    • DRAWDRAWDRAW

      长度为奇数,只有两个000,且最中间字符为000

      显然。ALICEALICEALICE如果不占领最中间字符,BOBBOBBOB就可以使用反转

    • ALICEALICEALICE

      疯狂反转强制BOBBOBBOB支付花费直到串变为含有奇数个000的回文串或者还差一步就变成偶数个零的回文串

      • 串是含有奇数个零的回文串,ALICEALICEALICE拥有必胜策略

      • 串差一步花费就变成 含偶数个零的回文串(现在不是)

        执行花费,使得串成为含有偶数个零的回文串

        BOBBOBBOB现在为先手,在此情况下先手必定会多支付222

#include <cstdio>
#define maxn 1005
char s[maxn];
int n, T;int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %s", &n, s + 1 );bool flag = 1; int l = 1, r = n;while( l <= r ) {if( s[l] != s[r] ) {flag = 0;break;}else l ++, r --;}int cnt = 0;for( int i = 1;i <= n;i ++ )cnt += ( s[i] == '0' );if( flag ) {if( cnt == 0 ) printf( "DRAW\n" );else if( cnt == 1 ) printf( "BOB\n" );else if( cnt & 1 ) printf( "ALICE\n" );else printf( "BOB\n" );}else {if( cnt == 2 && ( n & 1 ) && ( s[( n + 1 ) >> 1] == '0' ) ) printf( "DRAW\n" );else printf( "ALICE\n" );}}	return 0;
}

  • cnt00:si=sn−i+1=0cnt_{00}:s_i=s_{n-i+1}=0cnt00:si=sni+1=0的个数
  • cnt01:si≠0cnt_{01}:s_i≠ 0cnt01:si=0的个数
  • mid=[n&1⋂smidpos=0]mid=[n\&1\bigcap s_{mid\ pos}=0]mid=[n&1smid pos=0]
  • rev=[lastoptwasoperation2]rev=[last\ opt\ was\ operation\ 2]rev=[last opt was operation 2]
  • dpcnt00,cnt01,mid,rev:minimumcostdifferencedp_{cnt_{00},cnt_{01},mid,rev}:minimum\ cost\ differencedpcnt00,cnt01,mid,rev:minimum cost difference

转移

  • rev=0⋂cnt01>0rev=0\bigcap cnt_{01}>0rev=0cnt01>0

    dpcnt00,cnt01,mid,rev=min{−dpcnt00,cnt01,mid,1}dp_{cnt_{00},cnt_{01},mid,rev}=min\{-dp_{cnt_{00},cnt_{01},mid,1}\}dpcnt00,cnt01,mid,rev=min{dpcnt00,cnt01,mid,1}

  • cnt00>0,cnt01→cnt00cnt_{00}>0,cnt_{01}\rightarrow cnt_{00}cnt00>0,cnt01cnt00

    dpcnt00,cnt01,mid,rev=min{1−dpcnt00+1,cnt01−1,mid,0}dp_{cnt_{00},cnt_{01},mid,rev}=min\{1-dp_{cnt_{00}+1,cnt_{01}-1,mid,0}\}dpcnt00,cnt01,mid,rev=min{1dpcnt00+1,cnt011,mid,0}

  • cnt01>0,cnt00→cnt01cnt_{01}>0,cnt_{00}\rightarrow cnt_{01}cnt01>0,cnt00cnt01

    dpcnt00,cnt01,mid,rev=min{1−dpcnt00+1,cnt01−1,mid,0}dp_{cnt_{00},cnt_{01},mid,rev}=min\{1-dp_{cnt_{00}+1,cnt_{01}-1,mid,0}\}dpcnt00,cnt01,mid,rev=min{1dpcnt00+1,cnt011,mid,0}

  • mid=1mid=1mid=1

    dpcnt00,cnt01,mid,rev=min{1−dpcnt00+1,cnt01−1,0,0}dp_{cnt_{00},cnt_{01},mid,rev}=min\{1-dp_{cnt_{00}+1,cnt_{01}-1,0,0}\}dpcnt00,cnt01,mid,rev=min{1dpcnt00+1,cnt011,0,0}

结果

  • dpcnt00,cnt01,mid,rev>0:BOBdp_{cnt_{00},cnt_{01},mid,rev}>0:BOBdpcnt00,cnt01,mid,rev>0:BOB
  • dpcnt00,cnt01,mid,rev<0:ALICEdp_{cnt_{00},cnt_{01},mid,rev}<0:ALICEdpcnt00,cnt01,mid,rev<0:ALICE
  • dpcnt00,cnt01,mid,rev=0:DRAWdp_{cnt_{00},cnt_{01},mid,rev}=0:DRAWdpcnt00,cnt01,mid,rev=0:DRAW

C. Sequence Pair Weight

考虑一对(i,j),{i<j,ai=aj}(i,j),\{i<j,a_i=a_j\}(i,j),{i<j,ai=aj}的贡献,这对(i,j)(i,j)(i,j)出现的序列组合数为i×(n−j+1)i\times (n-j+1)i×(nj+1)

贡献可以彼此独立拆分给i,ji,ji,j,从后往前扫记录一个后缀和即可

#include <cstdio>
#include <map>
using namespace std;
#define maxn 100005
#define int long long
map < int, int > mp;
int T, n;
int a[maxn];signed main() {scanf( "%lld", &T );while( T -- ) {mp.clear();scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &a[i] );int ans = 0;for( int i = n;i;i -- ) {ans += i * mp[a[i]];mp[a[i]] += ( n - i + 1 );}printf( "%lld\n", ans );}return 0;
}

D. MEX Tree

如果想要iii成为答案,那么这一条路径链上必须出现过[0,i−1][0,i-1][0,i1]内所有的点

那么我们完全可以维护出答案为iii时,左右链的链头位置,然后到i+1i+1i+1就需要把iii加入链

iii要么被之前维护的链路径包含,要么是链头某个的子树内一点,如果出现分叉就直接退出(后面不可能合法)

要注意如果两个链头成祖先关系

细节看代码比较好一点

在这里插入图片描述

图一

在这里插入图片描述

图二

#include <cstdio>
#include <vector>
using namespace std;
#define maxn 200005
#define int long long
vector < int > G[maxn];
int T, n, cnt;
int siz[maxn], dep[maxn], l[maxn], r[maxn], ans[maxn];
bool vis[maxn];
int f[maxn][20];void dfs( int u, int fa ) {siz[u] = 1, dep[u] = dep[fa] + 1, f[u][0] = fa;for( int i = 1;i < 20;i ++ )f[u][i] = f[f[u][i - 1]][i - 1];l[u] = ++ cnt;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;else dfs( v, u ), siz[u] += siz[v];}r[u] = cnt;
}int get_top( int u, int t ) {for( int i = 19;~ i;i -- )if( dep[f[u][i]] > dep[t] ) u = f[u][i];return u;
}bool check( int u, int v ) { return l[u] <= l[v] && r[v] <= r[u];
}signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld", &n );for( int i = 0;i <= n + 1;i ++ ) {G[i].clear(), vis[i] = 0, ans[i] = 0;for( int j = 0;j < 20;j ++ ) f[i][j] = 0;}cnt = 0;for( int i = 1, u, v;i < n;i ++ ) {scanf( "%lld %lld", &u, &v );G[u].push_back( v );G[v].push_back( u );}dfs( 0, 0 );ans[0] = n * ( n - 1 ) / 2;for( int i = 0, s = 1;i < G[0].size();i ++ )ans[1] += s * siz[G[0][i]], s += siz[G[0][i]];int x = 0, y = 0; vis[0] = 1;for( int i = 1;i <= n;i ++ ) {if( vis[i] ) {ans[i + 1] = ans[i];continue;}
/*
x_son:x是1否0是y祖先
y_son:y是1否0是x祖先
x_top:y如果是x祖先 则x属于y的直系儿子x_top家族
y_top:x如果是y祖先 则y属于x的直系儿子y_top家族
*/int x_son = check( x, y ), y_son = check( y, x ), x_top = get_top( x, y ), y_top = get_top( y, x );if( ( x == y ) || ( x_son && check( x, i ) && ! check( y_top, i ) ) || ( ! x_son && check( x, i ) ) ) {
/*
case1:x=y随便归一类(就是刚开始的x=y=0而已)
case2:x是y祖先 且 x也是i祖先 且y_top不能是i祖先(i不是x-y路径上的[vis判断] 也就是x-i,x-y是个二叉路径 不满足链要求) 图1
case3:x不是y祖先 且 x是i祖先 图2
*/int t = i;while( t != x ) vis[t] = 1, t = f[t][0];x = i;}else if( ( y_son && check( y, i ) && ! check( x_top, i ) ) || ( ! y_son && check( y, i ) ) ) {
//与x类似int t = i;while( t != y ) vis[t] = 1, t = f[t][0];y = i;} else break;//x,y已经改变了x_top = get_top( x, y ), y_top = get_top( y, x );if( check( x, y ) ) ans[i + 1] = ( siz[x] - siz[y_top] ) * siz[y];else if( check( y, x ) ) ans[i + 1] = ( siz[y] - siz[x_top] ) * siz[x];else ans[i + 1] = siz[x] * siz[y];}for( int i = 0;i <= n;i ++ )printf( "%lld ", ans[i] - ans[i + 1] );printf( "\n" );}	return 0;
}

E. Partition Game

dpi,k:divide[1,i]intokpartsminimumcostdp_{i,k}:divide\ [1,i]\ into\ k\ parts\ minimum\ costdpi,k:divide [1,i] into k parts minimum cost

dpi,k=minj<i(dpj,k−1+w(j+1,i))dp_{i,k}=min_{j<i}\bigg(dp_{j,k-1}+w(j+1,i)\bigg)dpi,k=minj<i(dpj,k1+w(j+1,i))

thekeypointishowtoworkoutw(j+1,i)the\ key\ point\ is\ how\ to\ work\ out\ w(j+1,i)the key point is how to work out w(j+1,i)

kkk分层做,考虑当第kkk层时,fi→fi+1f_i\rightarrow f_{i+1}fifi+1Δ\DeltaΔ

只多了ai+1a_{i+1}ai+1一个数字,也就是说只有ai+1a_{i+1}ai+1的颜色贡献可能发生改变

preipre_iprei表示与iii颜色相同的前驱位置

  • j∈[prei,i)j∈[pre_i,i)j[prei,i)时,是不会有贡献产生的,因为[j+1,i][j+1,i][j+1,i]里面没有跟aia_iai一样的;

  • j∈[1,prei)j∈[1,pre_i)j[1,prei)时,aia_iai最后出现的位置prei→i,Δ=i−preipre_i\rightarrow i,\Delta= i-pre_ipreii,Δ=iprei

区间fff整体增加Δ\DeltaΔ,用线段树维护即可

每层重新以上一层的信息建树

#include <cstdio>
#include <iostream>
using namespace std;
#define maxk 105
#define maxn 35005
#define inf 0x7f7f7f7f
int n, K;
int a[maxn], pre[maxn], pos[maxn];
int tag[maxn << 2], t[maxn << 2];
int f[maxn][maxk];void pushdown( int num ) {tag[num << 1] += tag[num];tag[num << 1 | 1] += tag[num];t[num << 1] += tag[num];t[num << 1 | 1] += tag[num];tag[num] = 0;
}void modify( int num, int l, int r, int L, int R, int v ) {if( L > R || L > r || l > R ) return;if( L <= l && r <= R ) {tag[num] += v;t[num] += v;return;}pushdown( num );int mid = ( l + r ) >> 1;modify( num << 1, l, mid, L, R, v );modify( num << 1 | 1, mid + 1, r, L, R, v );t[num] = min( t[num << 1], t[num << 1 | 1] );
}int query( int num, int l, int r, int L, int R ) {if( L > r || l > R ) return inf;if( L <= l && r <= R ) return t[num];pushdown( num );int mid = ( l + r ) >> 1;return min( query( num << 1, l, mid, L, R ), query( num << 1 | 1, mid + 1, r, L, R ) );
}void build( int num, int l, int r, int k ) {tag[num] = 0;//标记不清空 亲人两行泪 if( l == r ) {t[num] = f[l][k];return;}int mid = ( l + r ) >> 1;build( num << 1, l, mid, k );build( num << 1 | 1, mid + 1, r, k );t[num] = min( t[num << 1], t[num << 1 | 1] );
}int main() {scanf( "%d %d", &n, &K );for( int i = 1;i <= n;i ++ ) {scanf( "%d", &a[i] );pre[i] = pos[a[i]];pos[a[i]] = i;f[i][1] = f[i - 1][1] + ( pre[i] ? i - pre[i] : 0 );}for( int k = 2;k <= K;k ++ ) {build( 1, 1, n, k - 1 );for( int i = k + 1;i <= n;i ++ ) { modify( 1, 1, n, 1, pre[i] - 1, i - pre[i] );f[i][k] = query( 1, 1, n, 1, i - 1 );}}printf( "%d\n", f[n][K] );return 0;
}

#706 (Div. 2)——1496

A. Split it!

#include <cstdio>
#define maxn 105
int T, n, k;
char s[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d %s", &n, &k, s + 1 );int len = 0;for( int i = 1;i <= ( n >> 1 );i ++ )if( s[i] == s[n - i + 1] ) len = i;else break;	if( len > k || ( len == k && ( len << 1 ) < n ) ) printf( "YES\n" );else printf( "NO\n" );}return 0;
}

B. Max and Mex

不难发现,如果整个序列没有填充完[0,n)[0,n)[0,n),那么最多只会增加一个(mexmexmex不变)

否则则是n,n+1,n+2...重复操作

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 500005
int T, n, k;
int a[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d", &n, &k );for( int i = 0;i < n;i ++ )scanf( "%d", &a[i] );sort( a, a + n );if( ! k ) {int tot = unique( a, a + n ) - a;printf( "%d\n", tot );continue;}int x;for( int i = 0, idx = 0;idx < n;i ++ )while( a[idx] != i && idx < n ) x = i, idx ++;int y = a[n - 1];int val = ( x + y + 1 ) >> 1;if( val != x ) {a[n ++] = val;sort( a, a + n );int tot = unique( a, a + n ) - a;printf( "%d\n", tot );}else {int tot = unique( a, a + n ) - a;printf( "%d\n", k + tot );}}return 0;
}

C. Diamond Miner

ball哥的排序不等式

最小跟最小,次小跟次小,…,依次类推(注意全部转化到xxx的正半轴,因为大小是绝对值的大小)

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 200005
double x[maxn], y[maxn], p[maxn], v[maxn];
int T, n;int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );int cnt1 = 0, cnt2 = 0;for( int i = 1;i <= ( n << 1 );i ++ ) {scanf( "%lf %lf", &x[i], &y[i] );if( x[i] < 0 ) x[i] = -x[i]; else;if( y[i] < 0 ) y[i] = -y[i]; else;if( x[i] == 0 ) p[++ cnt1] = y[i];else v[++ cnt2] = x[i];}sort( p + 1, p + n + 1 );sort( v + 1, v + n + 1 );double ans = 0;for( int i = 1;i <= n;i ++ )ans += sqrt( p[i] * p[i] + v[i] * v[i] );printf( "%.10f\n", ans );}return 0;
}

D. Let’s Go Hiking

很容易将条件转换为有向边关系,最后只有可能最长链有两条并且边数为偶数(点数为奇数)才可能是顶点逃脱

也就是说答案只能为0/1

A1<A2<A3<...<Ak>Ak+1>Ak+2>...Ak−1A_1<A_2<A_3<...<A_k>A_{k+1}>A_{k+2}>...A_{k-1}A1<A2<A3<...<Ak>Ak+1>Ak+2>...Ak1

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 100005
vector < int > x[maxn], y[maxn];
int n;
int p[maxn], d[maxn];int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &p[i] );for( int i = 2;i <= n;i ++ )d[i] = ( p[i - 1] < p[i] );int len = 0, cnt = 0;for( int l = 2, r = 2;l <= n;l = r = r + 1 ) {while( r + 1 <= n && d[l] == d[r + 1] ) r ++;if( r - l + 2 > len ) len = r - l + 2, cnt = 1;else if( r - l + 2 == len ) cnt ++;}if( cnt != 2 || ! ( len & 1 ) ) return ! printf( "0\n" );bool flag = 0;for( int i = 3;i <= n;i ++ ) {if( d[i - 1] && ! d[i] ) {int l = i - 1, r = i;while( l > 2 && d[l - 1] ) l --;while( r < n && ! d[r + 1] ) r ++;if( ( i - 1 ) - ( l - 1 ) + 1 == len && r - ( i - 1 ) + 1 == len ) {flag = 1;break;}}}printf( "%d\n", flag );return 0;
}

#722 (Div. 1)——1528

A. Parsa’s Humongous Tree

贪心地有,只可能选lv/rvl_v/r_vlv/rv

dpi,0/1dp_{i,0/1}dpi,0/1表示点iii选择lil_ili还是rir_iri

DPDPDP即可

#include <cstdio>
#include <vector>
using namespace std;
#define maxn 100005
#define int long long
vector < int > G[maxn];
int T, n, ans;
int l[maxn], r[maxn];
int dp[maxn][2];int Fabs( int x ) {return x < 0 ? -x : x;
}void dfs( int u, int fa ) {for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;else {dfs( v, u );dp[u][0] += max( dp[v][0] + Fabs( l[u] - l[v] ), dp[v][1] + Fabs( l[u] - r[v] ) );dp[u][1] += max( dp[v][0] + Fabs( r[u] - l[v] ), dp[v][1] + Fabs( r[u] - r[v] ) );}}
}signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%lld %lld", &l[i], &r[i] );dp[i][0] = dp[i][1] = 0;G[i].clear();}for( int i = 1, u, v;i < n;i ++ ) {scanf( "%lld %lld", &u, &v );G[u].push_back( v );G[v].push_back( u );		}dfs( 1, 0 );printf( "%lld\n", max( dp[1][0], dp[1][1] ) );}return 0;
}

B. Kavi on Pairing Duty

dpn:2ndp_n:2ndpn:2n个点的方案数

如果1↔n+1;2↔n+2;...;n↔2n1\leftrightarrow n+1;2\leftrightarrow n+2;...;n\leftrightarrow 2n1n+1;2n+2;...;n2n,点数全部使用完,相当于子任务的dp0dp_0dp0

如果1↔n+2;2↔n+3;...;n−2↔2n1\leftrightarrow n+2;2\leftrightarrow n+3;...;n-2\leftrightarrow 2n1n+2;2n+3;...;n22n,点对剩下(n,n+1)(n,n+1)(n,n+1),相当于子任务的dp1dp_1dp1

以此类推,一直到1↔2n1\leftrightarrow 2n12n,相当于子任务的dpn−1dp_{n-1}dpn1

所以是个前缀和

但是还没完,还可能有将nnn划分成长度相等的若干段,然后[n+1,2n][n+1,2n][n+1,2n]完全复制[1,n][1,n][1,n]的划法

那么这就要求长度是nnn的因数

#include <cstdio>
#define int long long
#define mod 998244353
#define maxn 1000005
int n, sum;
int dp[maxn];signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )for( int j = ( i << 1 );j <= n;j += i )dp[j] ++;dp[0] = sum = 1;for( int i = 1;i <= n;i ++ ) {dp[i] = ( dp[i] + sum ) % mod;sum = ( sum + dp[i] ) % mod;}printf( "%lld\n", dp[n] );return 0;
}

C. Trees of Tranquillity

最终答案集合中任意两个顶点都必须有边,转化一下答案集合

soroush树上某一条链上的部分顶点,且这些点在keshi树上的管辖区间不交

判断是否是祖先关系,可以用dfndfndfn序列管辖范围lu≤lv⋂rv≤rul_u\le l_v\bigcap r_v\le r_ululvrvru进行判断。对keshidfsdfsdfs

soroush进行dfsdfsdfs搜树,天然就有路径是一条链(第一次经过,加入;第二次经过,删除)

再根据keshi的管辖范围进行点的选择,用setsetset维护

欧拉序区间只存在包含或者不交的关系,如果有包含ta的区间直接替换,否则直接加入

#include <set>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 300005
set < pair < int, int > > st;
set < pair < int, int > > :: iterator it, ori;
vector < int > soroush[maxn],  keshi[maxn];
int T, n, now, ans, cnt;
int l[maxn], r[maxn];void dfs_keshi( int u ) {l[u] = ++ cnt;for( int i = 0;i < keshi[u].size();i ++ )dfs_keshi( keshi[u][i] );r[u] = cnt;
}bool no_ancestor( int u, int v ) {return ( l[u] <= l[v] && r[v] <= r[u] ) ^ 1;
}void dfs_soroush( int u ) {int lst = now;it = st.lower_bound( make_pair( l[u], 0 ) );if( it != st.end() ) now += no_ancestor( u, it -> second );if( it != st.begin() ) {ori = it; it --;now += no_ancestor( it -> second, u );if( ori != st.end() )now -= no_ancestor( it -> second, ori -> second );}ans = max( ans, now );st.insert( make_pair( l[u], u ) );for( int i = 0;i < soroush[u].size();i ++ ) dfs_soroush( soroush[u][i] );st.erase( make_pair( l[u], u ) );now = lst;
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )soroush[i].clear(), keshi[i].clear();for( int i = 2, x;i <= n;i ++ ) {scanf( "%d", &x );soroush[x].push_back( i );}for( int i = 2, x;i <= n;i ++ ) {scanf( "%d", &x );keshi[x].push_back( i );}ans = now = cnt = 0;dfs_keshi( 1 );dfs_soroush( 1 );printf( "%d\n", ans + 1 );}return 0;
}

D. It’s a bird! No, it’s a plane! No, it’s AaParsa!

普通的最短路dijkstra与此题出入在于在城市等待炮车转向的时间

不妨对于每个城市iii都与其下一个城市(i+1)%n(i+1)\%\ n(i+1)% n连一条111的伪边,伪边不随炮车转向

稍修改一下最短路的转移,i→j⇒i→(j+Di)%ni\rightarrow j\Rightarrow i\rightarrow (j+D_i)\%\ niji(j+Di)% n,即加上到iii的时间(此炮车转向后指的城市)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define int long long
#define maxn 605
int n, m;
int c[maxn][maxn];
bool vis[maxn];
int dis[maxn];signed main() {memset( c, 0x7f, sizeof( c ) );scanf( "%lld %lld", &n, &m );for( int i = 1, u, v, w;i <= m;i ++ ) {scanf( "%lld %lld %lld", &u, &v, &w );c[u][v] = w;}for( int t = 0;t < n;t ++ ) {memset( vis, 0, sizeof( vis ) );for( int i = 0;i < n;i ++ )dis[i] = c[t][i];for( int i = 1;i <= n;i ++ ) {int k = -1;for( int j = 0;j < n;j ++ )if( vis[j] ) continue;else if( k == -1 || dis[j] < dis[k] ) k = j;if( k == -1 ) break;vis[k] = 1;dis[( k + 1 ) % n] = min( dis[( k + 1 ) % n], dis[k] + 1 );for( int j = 0;j < n;j ++ )dis[( j + dis[k] ) % n] = min( dis[( j + dis[k] ) % n], dis[k] + c[k][j] );}for( int i = 0;i < n;i ++ )if( i == t ) printf( "0 " );else printf( "%lld ", dis[i] );printf( "\n" );}return 0;
}

E. Mashtali and Hagh Trees

假定根,含有0/2条入边,其他节点的出边方向等于根的入边方向

容易发现,每棵符合要求的树的根都满足该条件(可以通过暂时根一直走入边走到真正的根上)

符合要求的树,根也是唯一确定的

若一棵上有≥2\ge 22个点拥有≥2\ge 22入边,则一定能找到一对点对没有公共朋友(不合法)

将最后答案的树分为三类

  • 所有边与指向根节点
  • 所有边与指向根节点的方向相反
  • 两棵子树的边方向都指向根,第三棵子树的边方向相反且不为链

一二类可以合并讨论,无非就是反向的问题

易得,每个节点(除根外)儿子个数≤2\le 22

fi:f_i:fi: 深度为iii(相当于最长路径为iii)的满足条件的树方案数,prei=∑j=1ifjpre_i=\sum_{j=1}^if_jprei=j=1ifj

fi=fi−1+fi−1×prei−2+fi−1×(fi−1+1)2f_i=f_{i-1}+f_{i-1}\times pre_{i-2}+\frac{f_{i-1}\times (f_{i-1}+1)}{2}fi=fi1+fi1×prei2+2fi1×(fi1+1)

  • 只有一个儿子

    fi−1f_{i-1}fi1

  • 有两个儿子

    • 一个深度恰好为i−1i-1i1,另一个深度<i−1<i-1<i1

      fi−1×prei−2f_{i-1}\times pre_{i-2}fi1×prei2

    • 有两个儿子,两个深度都为i−1i-1i1,排除掉同构和左右儿子调换重复枚举的方案

      son1:1↔son2:1son_1:1 \leftrightarrow son_2:1son1:1son2:1

      son1:2↔son2:1,2son_1:2 \leftrightarrow son_2:1,2son1:2son2:1,2

      son1:3↔son2:1,2,3son_1:3 \leftrightarrow son_2:1,2,3son1:3son2:1,2,3

      son1:fi↔son2:1,2,3,4,...,fison_1:f_i \leftrightarrow son_2:1,2,3,4,...,f_ison1:fison2:1,2,3,4,...,fi

      等差数列求和

      fi−1×(fi−1+1)2\frac{f_{i-1}\times (f_{i-1}+1)}{2}2fi1×(fi1+1)

gi:g_i:gi: 深度为iii且根度数一定为222的方案数,易得gi=fi−fi−1g_i=f_i-f_{i-1}gi=fifi1

可以得出,一二类的答案
ans1=2(fn+fn−1(fn−1+1)pren−22+fn−1pren−2(pren−2+1)2+fn−1(fn−1+1)(fn−1+2)6)−1ans_1=2(f_n+\frac{f_{n-1}(f_{n-1}+1)pre_{n-2}}{2}+\frac{f_{n-1}pre_{n-2}(pre_{n-2}+1)}{2}+\frac{f_{n-1}(f_{n-1}+1)(f_{n-1}+2)}{6})-1 ans1=2(fn+2fn1(fn1+1)pren2+2fn1pren2(pren2+1)+6fn1(fn1+1)(fn1+2))1

  • 只有1/21/21/2个儿子的情况

    fnf_nfn

  • 333个儿子

    • 两个儿子的深度n−1n-1n1,第三个深度<n−1<n-1<n1,同样排除掉同构和左右儿子调换重复枚举的方案

      fn−1(fn1+1)2⋅pren−2\frac{f_{n-1}(f_{n_1}+1)}{2}·pre_{n-2}2fn1(fn1+1)pren2

    • 一个儿子深度n−1n-1n1,两个儿子深度<n−1<n-1<n1

      fn−1⋅pren−1(pren−1+1)2f_{n-1}·\frac{pre_{n-1}(pre_{n-1}+1)}{2}fn12pren1(pren1+1)

    • 三个儿子深度都为n−1n-1n1,排除掉同构和左右儿子调换重复枚举的方案

      fn−1(fn−1+1)(fn−2+2)6\frac{f_{n-1}(f_{n-1}+1)(f_{n-2}+2)}{6}6fn1(fn1+1)(fn2+2)

×2\times 2×2是给树定向,−1-11是减去同构的单链(方向不同的单链也是同构的)

到这里已经成功一半了,最后来解决一下第三类树的计算

对于第三类树,相当于在一棵已有两个儿子的树上再填一棵子树,枚举子树的深度

(从第三棵子树到根再到一二棵子树的最长路径必须为nnn:所以枚举第三棵子树深度(路径)iii,过nnn路径111,一二子树必须至少有一个深度(路径)为n−i−1n-i-1ni1,这样才能构成单向长度为nnn的路径)

ans2=∑i=0n−1(fi−1)gn−i−1ans_2=\sum_{i=0}^{n-1}(f_i-1)g_{n-i-1}ans2=i=0n1(fi1)gni1

每个fif_ifi−1-11是为了减去单链的情况

最终答案为ans1+ans2ans_1+ans_2ans1+ans2

#include <cstdio>
#define int long long
#define maxn 1000005
#define mod 998244353
int n, inv2, inv6;
int f[maxn], pre[maxn], g[maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans;
}signed main() {inv2 = qkpow( 2, mod - 2 ), inv6 = qkpow( 6, mod - 2 );scanf( "%lld", &n );f[0] = pre[0] = g[1] = 1, f[1] = 2, pre[1] = 3;if( n == 1 ) return ! printf( "5\n" );for( int i = 2;i <= n;i ++ ) {f[i] = ( f[i - 1] + f[i - 1] * pre[i - 2] % mod + f[i - 1] * ( f[i - 1] + 1 ) % mod * inv2 % mod ) % mod;pre[i] = ( pre[i - 1] + f[i] ) % mod;g[i] = ( f[i] - f[i - 1] + mod ) % mod;}int ans1 = ( f[n] + f[n - 1] * ( f[n - 1] + 1 ) % mod * pre[n - 2] % mod * inv2 % mod+ f[n - 1] * pre[n - 2] % mod * ( pre[n - 2] + 1 ) % mod * inv2 % mod+ f[n - 1] * ( f[n - 1] + 1 ) % mod * ( f[n - 1] + 2 ) % mod * inv6 % mod )% mod;ans1 = ( 2 * ans1 - 1 + mod ) % mod;int ans2 = 0;for( int i = 0;i < n;i ++ )ans2 = ( ans2 + ( f[i] - 1 ) * g[n - i - 1] % mod + mod ) % mod;printf( "%lld\n", ( ans1 + ans2 ) % mod );return 0;
}

#723 (Div. 2)——1526

A. Mean Inequality

第一题也不会出得多么卡人,双指针从最小到最大交替输出即可

B. I Hate 1111

吐了啊,差点没做出来

不难发现,1111=11∗101,11111=111∗100+11∗1......1111=11*101,11111=111*100+11*1......1111=11101,11111=111100+111......看似是多个的111最后都能被11,11111,11111,111凑出来

所以只需要判断能否写成11x+111y=n(x,y≥0)11x+111y=n(x,y\ge 0)11x+111y=n(x,y0)的形式即可

再有yyy是不会超过111111的,多的就划分给xxx,所以枚举yyy判断是否在某一个值时刻是111111的倍数

C. Potions

n≤2000n\le 2000n2000,直接O(n2)dpO(n^2)dpO(n2)dp,与套路型不同,最基础的dpdpdp就是带着魔力值转移,但显然不行

dpi,j:dp_{i,j}:dpi,j: 在第iii瓶魔水时,一共喝了jjj瓶魔水的最大健康值

在转移时需要保证此时健康值必须非负,最后答案看最大的jjj满足dpi,j≥0dp_{i,j}\ge 0dpi,j0

n≤200000n\le 200000n200000,很明显是想卡n2→nlog⁡nn^2\rightarrow n\log nn2nlogn,想到了古老时代小伙子杂题练习时的一道流水生产线贪心

最大反悔贪心

iii时刻先选了再说,然后每次丢最大消耗的魔水直到健康值为非负(保证了每时每刻都是健康非负)

D. Kill Anton

atcoder某题类似,那道题是要求移动的最小次数——同一类依次配对移动,运用到这里就是尽可能让同一类配对距离最大,因此字符一定是连续相等为一段(感性理解),证明上自家OJ

直接暴力枚举四种字符的排列情况,相当于是求逆序对个数

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
#define int long long
int T, n;
char s[maxn], ch[5] = { 'A', 'N', 'O', 'T' }, ret[5];
//注意ch[0~3]字符顺序需要和id(c)一一匹配
//卡了一个小时 woc
int cnt[5];
bool vis[maxn];int id( char c ) {switch( c ) {case 'A' : return 0;case 'N' : return 1;case 'O' : return 2;case 'T' : return 3;}
}int work( char *p ) {int ans = 0;for( int i = 1;i <= n;i ++ ) vis[i] = 0;for( int i = 0;i < 4;i ++ ) {int l = 1, r = 0;for( int j = 1;j <= n;j ++ )if( vis[j] ) continue;else {++ r;if( p[i] == s[j] )ans += r - l, l ++, vis[j] = 1;else;}}return ans;
}signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%s", s + 1 );n = strlen( s + 1 );memset( cnt, 0, sizeof( cnt ) );for( int i = 1;i <= n;i ++ )cnt[id( s[i] )] ++;int ans = -1;do {int now = work( ch );if( now > ans ) {ans = now;memcpy( ret, ch, sizeof( ch ) );}} while( next_permutation( ch, ch + 4 ) );for( int i = 0;i < 4;i ++ )for( int j = 1;j <= cnt[id( ret[i] )];j ++ )printf( "%c", ret[i] );printf( "\n" );}return 0;
}

Deltix Round, Spring 2021 (Div. 1 + Div. 2)——1523

A. Game of Life

除了模拟一无是处

#include <cstdio>
#define maxn 1005
int T, n, m;
char s[maxn];
int q[2][maxn], vis[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d %s", &n, &m, s + 1 );s[n + 1] = s[0] = '0';q[1][0] = 0;for( int i = 1;i <= n;i ++ )vis[i] = 0;for( int i = 1;i <= n;i ++ )if( s[i] == '1' ) q[1][++ q[1][0]] = i;for( int t = 1;t <= m;t ++ ) {int k = t & 1;q[k ^ 1][0] = 0;for( int i = 1;i <= q[k][0];i ++ ) {int p = q[k][i];if( p > 1 && ( vis[p - 2] == t || s[p - 2] == '0' ) && s[p - 1] == '0' ) s[p - 1] = '1', vis[p - 1] = t, q[k ^ 1][++ q[k ^ 1][0]] = p - 1;if( p < n && ( vis[p + 2] == t || s[p + 2] == '0' ) && s[p + 1] == '0' )s[p + 1] = '1', vis[p + 1] = t, q[k ^ 1][++ q[k ^ 1][0]] = p + 1;}if( ! q[k ^ 1][0] ) break;}for( int i = 1;i <= n;i ++ )printf( "%c", s[i] );printf( "\n" );}	return 0;
}

B. Lord of the Values

手玩出奇迹,方法多种多样

#include <cstdio>
#define maxn 1005
int T, n;
int a[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &a[i] );printf( "%d\n", n * 3 );for( int i = 1;i <= n;i += 2 ) {printf( "2 %d %d\n", i, i + 1 );//ai,aj -> ai,aj-aiprintf( "1 %d %d\n", i, i + 1 );//ai,aj-ai -> aj,aj-aiprintf( "1 %d %d\n", i, i + 1 );//aj,aj-ai -> aj+aj-ai,aj-aiprintf( "2 %d %d\n", i, i + 1 );//aj+aj-ai,aj-ai -> aj+aj-ai,-ajprintf( "1 %d %d\n", i, i + 1 );//aj+aj-ai,-aj -> aj-ai,-ajprintf( "1 %d %d\n", i, i + 1 );//aj-ai,-aj -> -ai,-aj}}return 0;
}

C. Compression and Expansion

读懂题的模拟才是王道

#include <cstdio>
#define maxn 2000
int T, n;
int num[maxn];int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d", &n );int dep = 1;num[dep] = 1;for( int i = 1, x;i <= n;i ++ ) {scanf( "%d", &x );if( x == 1 && i == 1 ) goto print;else if( x == 1 ) num[++ dep] = 1;else {while( x != num[dep] + 1 ) dep --;num[dep] = x;}print :printf( "%d", num[1] );for( int i = 2;i <= dep;i ++ )printf( ".%d", num[i] );printf( "\n" );}}return 0;
}

D. Love-Hate

最后要求人数≥⌈n2⌉\ge \lceil\frac{n}{2}\rceil2n,从这里入手

随机数选取一个人,假定这个人一定是被最后答案所包含的,那么最后答案的货币就是这个人喜爱的货币子集,选一个人不在最后答案范围的概率是12\frac{1}{2}21,抽取五十次,还抽不到概率就是1250\frac{1}{2^{50}}2501

这都能被卡,建议买彩票

然后直接抽出这个人的喜爱货币,然后枚举人数统计,并对子集的答案进行人数统计

也有用bitset暴力搜索剪枝的,只能说句nb

#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define int long long
#define maxn 200005
#define MAXN 32780
#define maxm 65
int n, m, p;
char ch[maxm];
int c[maxn], num[maxm], tot[MAXN];signed main() {srand( time( 0 ) ); scanf( "%lld %lld %lld", &n, &m, &p );for( int i = 1;i <= n;i ++ ) {scanf( "%s", ch );for( int j = 0;j < m;j ++ )c[i] = ( c[i] << 1 ) | ( ch[j] - '0' );}int ans = 0, cnt = 0;for( int Case = 1, t;Case <= 50;Case ++ ) {p = rand() * rand() % n + 1, t = 0;for( int i = 0;i < m;i ++ )if( c[p] >> i & 1 ) num[++ t] = i;memset( tot, 0, sizeof( tot ) );for( int i = 1;i <= n;i ++ ) {int s = 0;for( int j = 1;j <= t;j ++ )if( c[i] >> num[j] & 1 ) s |= ( 1 << j - 1 );tot[s] ++;}for( int j = t;j;j -- )for( int i = ( 1 << t ) - 1;~ i;i -- )if( i >> j - 1 & 1 ) tot[i ^ ( 1 << j - 1 )] += tot[i];int maxx = 0;for( int i = 0;i < ( 1 << t );i ++ )if( tot[i] < ( ( n + 1 ) >> 1 ) ) continue;else if( __builtin_popcount( i ) > __builtin_popcount( maxx ) )maxx = i;else;if( __builtin_popcount( maxx ) > cnt ) {cnt = __builtin_popcount( maxx );int now = 0;for( int i = 1;i <= t;i ++ )if( maxx >> i - 1 & 1 ) now |= ( 1ll << num[i] );ans = now;}}for( int i = m - 1;~ i;i -- )printf( "%lld", ans >> i & 1 );return 0;
}

E. Crypto Lights

E(numberof′1′)=E(option)=E(optionbeforestop)+1E(number\ of\ '1')=E(option)=E(option\ before\ stop)+1E(number of 1)=E(option)=E(option before stop)+1

假设操作了iii次,那么每两个111之间至少有k−1k-1k1000,也就是说至少有(i−1)(k−1)(i-1)(k-1)(i1)(k1)000

剩下的位置就随机放iii111,放的顺序不同算不同方案数,需要带阶乘

所以在停止前操作iii次的方案数为Cn−(i−1)(k−1)i×i!C_{n-(i-1)(k-1)}^i\times i!Cn(i1)(k1)i×i!

概率是从nnn个位置中选111个,从n−1n-1n1个位置中选111个,..................,从n−i+1n-i+1ni+1个位置中选111

1n⋅1n−1⋅1n−2⋅⋅⋅1n−i+1=(n−i)!n!\frac{1}{n}·\frac{1}{n-1}·\frac{1}{n-2}···\frac{1}{n-i+1}=\frac{(n-i)!}{n!}n1n11n21ni+11=n!(ni)!

ans=∑i=1nCn−(i−1)(k−1)i⋅i!⋅(n−i)!n!ans=\sum_{i=1}^n\frac{C_{n-(i-1)(k-1)}^i·i!·(n-i)!}{n!}ans=i=1nn!Cn(i1)(k1)ii!(ni)!

最后加上111即可

#include <cstdio>
#define int long long
#define mod 1000000007
#define maxn 100000
int T, n, k;
int fac[maxn + 5], inv[maxn + 5];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans;
}int C( int n, int m ) {if( n <= 0 || n < m ) return 0;else return fac[n] * inv[m] % mod * inv[n - m] % mod;
}signed main() {fac[0] = inv[0] = 1;for( int i = 1;i <= maxn;i ++ )fac[i] = fac[i - 1] * i % mod;inv[maxn] = qkpow( fac[maxn], mod - 2 );for( int i = maxn - 1;i;i -- )inv[i] = inv[i + 1] * ( i + 1 ) % mod;scanf( "%lld", &T );while( T -- ) {	scanf( "%lld %lld", &n, &k );int ans = 0;for( int i = 1;i <= n;i ++ )ans = ( ans + C( n - ( k - 1 ) * ( i - 1 ), i ) * fac[i] % mod * fac[n - i] % mod * inv[n] % mod ) % mod;printf( "%lld\n", ( ans + 1 ) % mod );}return 0;
}

F. Favorite Game

首先地标按时间排序,就可以DPDPDP

一般的,设dps,i,j:dp_{s,i,j}:dps,i,j: 传送门开关集合为sss,完成最大任务数为iii,在地点jjj的最少时间

很不幸的,状态数高达2nm22^nm^22nm2,已经是1e81e81e8的级别了

observation

  • 显然这四个量缺一不可
  • 如果知道了传送门的集合,我们就并不关心是在这集合中的哪个传送门处
  • 如果知道地标位置,时间也就不再重要(一定待到该地标对应的ttt

fs,i:f_{s,i}:fs,i: 传送门状态sss,完成最大任务数为iii在传送门处的最少时间

gs,i:g_{s,i}:gs,i: 传送门状态sss在地标i处的最大完成任务数

ws,i:w_{s,i}:ws,i: 传送门状态sss,从任何一个(开启)传送塔到地点i处(地标和传送门)的最少时间

  • 从某个(开启)传送门到新(关闭)传送门

    fs,i→fs∣(1<<j),i+ws,jf_{s,i}\rightarrow f_{s|(1<<j),i}+w_{s,j}fs,ifs(1<<j),i+ws,j

  • 从某个(开启)传送门到地标jjj

    [fs,i+ws,j≤tj]i+1→gs,j\bigg[{f_{s,i}+w_{s,j}\le t_j}\bigg]i+1\rightarrow g_{s,j}[fs,i+ws,jtj]i+1gs,j

  • 从地标jjj到传送门iii

    tj+min(disj,i,ws,i)→fs∣(1<<i),gs,jt_j + min( dis_{ j, i}, w_{s,i} )\rightarrow f_{s | ( 1 << i ),g_{s,j}}tj+min(disj,i,ws,i)fs(1<<i),gs,j

  • 从地标jjj到新地标iii

    [min(ws,i,disi,j)+tj≤ti]gs,j+1→gs,i\bigg[min(w_{s,i},dis_{i,j})+t_{j}\le t_i\bigg]g_{s,j}+1\rightarrow g_{s,i}[min(ws,i,disi,j)+tjti]gs,j+1gs,i

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define inf 0x7f7f7f7f
#define int long long
#define maxn 16400
#define maxm 120
struct node {int x, y, t;node(){}node( int X, int Y, int T ) {x = X, y = Y, t = T;}
}pos[maxm];
int n, m;
int f[maxn][maxm], g[maxn][maxm], w[maxn][maxm];bool cmp( node u, node v ) {return u.t < v.t;
}int Fabs( int x ) {return x < 0 ? -x : x;
}int dis( int i, int j ) {return Fabs( pos[i].x - pos[j].x ) + Fabs( pos[i].y - pos[j].y );
}signed main() {scanf( "%lld %lld", &n, &m );for( int i = 0;i < n;i ++ )scanf( "%lld %lld", &pos[i].x, &pos[i].y );for( int i = n;i < n + m;i ++ )scanf( "%lld %lld %lld", &pos[i].x, &pos[i].y, &pos[i].t );sort( pos, pos + n + m, cmp );int ans = 0;for( int s = 0;s < ( 1 << n );s ++ ) {for( int i = 0;i < n + m;i ++ ) {f[s][i] = w[s][i] = inf, g[s][i] = -inf;for( int j = 0;j < n;j ++ )if( s >> j & 1 ) w[s][i] = min( w[s][i], dis( j, i ) );}}for( int i = 0;i < n;i ++ ) f[1 << i][0] = 0;for( int i = 0;i < m;i ++ ) g[0][i] = 1;for( int s = 0;s < ( 1 << n );s ++ ) {//枚举传送塔开关状态for( int i = 0;i <= m;i ++ )//枚举完成最大任务数if( f[s][i] >= inf ) continue;else {for( int j = 0;j < n;j ++ )//传送塔到新传送塔if( s >> j & 1 ) continue;else f[s | ( 1 << j )][i] = min( f[s | ( 1 << j )][i], f[s][i] + w[s][j] );for( int j = 0;j < m;j ++ )//传送塔到新地标jif( f[s][i] + w[s][j + n] > pos[j + n].t ) continue;else g[s][j] = max( g[s][j], i + 1 );}for( int j = 0;j < m;j ++ )//枚举所在地标if( g[s][j] < 0 ) continue;else {for( int i = 0;i < n;i ++ )//从地标j到新传送塔i(要到过j才行pos[j+n].t)if( s >> i & 1 ) continue;else f[s | ( 1 << i )][g[s][j]] = min( f[s | ( 1 << i )][g[s][j]], pos[j + n].t + min( dis( j + n, i ), w[s][i] ) );for( int i = j + 1;i < m;i ++ )//从地标j到下一个地标iif( min( w[s][i + n], dis( j + n, i + n ) ) + pos[j + n].t > pos[i + n].t ) continue;else g[s][i] = max( g[s][i], g[s][j] + 1 );ans = max( ans, g[s][j] );}}printf( "%lld\n", ans );return 0;
}

AtCoder

2021-05-16(ARC 119)

A - 119 × 2^23 + 1

直接枚举BBB,指数范围只在[1,60][1,60][1,60]

B - Electric Board

发现两种操作其实都是针对000而言的,000可以左移可以右移

所以统计两个串000的个数和位置,iii个零的位置如果相等就可以不用动

至于怎么移动这个顺序是不用考虑的

#include <queue>
#include <cstdio>
using namespace std;
#define maxn 500005
queue < int > q1, q2;
int n, cnts, cntt;
char s[maxn], t[maxn];int main() {scanf( "%d %s %s", &n, s + 1, t + 1 );for( int i = 1;i <= n;i ++ )cnts += ( s[i] ^ 48 ), cntt += ( t[i] ^ 48 );if( cnts != cntt ) return ! printf( "-1\n" );int ans = 0;for( int i = 1;i <= n;i ++ ) {if( s[i] == '0' ) q1.push( i ); else;if( t[i] == '0' ) q2.push( i ); else;}while( ! q1.empty() ) {int p1 = q1.front(); q1.pop();int p2 = q2.front(); q2.pop();if( p1 != p2 ) ans ++; else;}printf( "%d\n", ans );return 0;
}

C - ARC Wrecker 2

找一段区间和为000的个数,奇偶随便划分正负即可

#include <cstdio>
#include <map>
#include <set>
using namespace std;
#define int long long
#define maxn 300005
set < int > s;
set < int > :: iterator it;
map < int, int > mp;
int n;
int A[maxn];signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &A[i] );int sum = 0;	mp[0] = 1;for( int i = 1;i <= n;i ++ ) {if( i & 1 ) sum += A[i];else sum -= A[i];mp[sum] ++;s.insert( sum );}int ans = 0;for( it = s.begin();it != s.end();it ++ ) {int cnt = mp[* it];ans += cnt * ( cnt - 1 ) / 2;}printf( "%lld\n", ans );return 0;
}

D - Grid Repainting 3

假设从(i,j)(i,j)(i,j)开始清空iii行,那么第iii行中其它的红色就自然是清空列,然后那些列上的其它红色又清空各自行…

就像一层一层的波浪往外扩,但最后可能回到最开始的iii,这个时候是不能清空的,否则iii就没了

也就是说,每一次“循环”都会损伤一行或者一列无法清空

将同行/同列的红色看做是有无向边连接的强联通,每个强联通都会损耗一行或者一列不能刷成白色

最后白色的个数就是(n−CntRow)∗(m−CntCol)(n-CntRow)*(m-CntCol)(nCntRow)(mCntCol)

#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define maxn 2505
vector < int > g;
int n, m, flag_x, flag_y, tot, cnt;
char c[maxn][maxn];
bool row[maxn], col[maxn];int dfs( int x, int y ) {bool Rx = row[x], Cy = col[y];row[x] = col[y] = 1;int ret = 0;if( ! Rx )for( int i = 0;i < m;i ++ )if( c[x][i] == 'R' ) ret += dfs( x, i );if( ! Cy )for( int i = 0;i < n;i ++ )if( c[i][y] == 'R' ) ret += dfs( i, y );if( Rx != Cy ) ret ++;return ret;
}void print( int x, int y ) {bool Rx = row[x], Cy = col[y];row[x] = col[y] = 1;if( ! Rx ) for( int i = 0;i < m;i ++ )if( c[x][i] == 'R' ) print( x, i );if( ! Cy )for( int i = 0;i < n;i ++ )if( c[i][y] == 'R' ) print( i, y );if( x == flag_x ) Rx = 1; else;if( y == flag_y ) Cy = 1; else;if( ! Rx && Cy ) printf( "X %d %d\n", x + 1, y + 1 );if( Rx && ! Cy ) printf( "Y %d %d\n", x + 1, y + 1 );
}int main() {scanf( "%d %d", &n, &m );for( int i = 0;i < n;i ++ )scanf( "%s", c[i] );for( int i = 0;i < n;i ++ )for( int j = 0;j < m;j ++ )if( c[i][j] == 'R' && ! row[i] && ! col[j] ) {tot += dfs( i, j );cnt ++;g.push_back( i * m + j );}if( ! cnt ) return ! printf( "0\n" );int cnt_row = 0, cnt_col = 0;for( int i = 0;i < n;i ++ ) cnt_row += row[i];for( int i = 0;i < m;i ++ ) cnt_col += col[i];int maxx = 0, pos;for( int i = 0;i <= cnt;i ++ ) {int x = n - cnt_row + i, y = m - cnt_col + cnt - i;if( maxx < n * m - x * y ) maxx = n * m - x * y, pos = i;}memset( row, 0, sizeof( row ) );memset( col, 0, sizeof( col ) );printf( "%d\n", tot + cnt );for( int i = 0;i < pos;i ++ ) {flag_x = g[i] / m, flag_y = -1;print( g[i] / m, g[i] % m );}for( int i = pos;i < cnt;i ++ ) {flag_x = -1, flag_y = g[i] % m;print( g[i] / m, g[i] % m );}return 0;
}

E - Pancakes

这种带绝对值的翻转连续区间类题目已经做过有几道了,不难发现,只与翻转区间的端点左右有关

翻转一段连续区间[l,r][l,r][l,r],就相当于答案更新∣Ar−Al−1∣+∣Ar+1−Al∣−∣Ar+1−Ar∣−∣Al−Al−1∣|A_r-A_{l-1}|+|A_{r+1}-A_l|-|A_{r+1}-A_r|-|A_{l}-A_{l-1}|ArAl1+Ar+1AlAr+1ArAlAl1

对于指定的[l,r][l,r][l,r]后两项只分别与l,rl,rl,r有关,是个定值

那么考虑∣Ar−Al−1∣+∣Ar+1−Al∣|A_r-A_{l-1}|+|A_{r+1}-A_l|ArAl1+Ar+1Al

暴力讨论一下,令x=Al,y=Ar,u=Al−1,v=Ar+1x=A_l,y=A_r,u=A_{l-1},v=A_{r+1}x=Al,y=Ar,u=Al1,v=Ar+1

  • x≤v,y≤u→u−y+v−x=(u+v)−(x+y)x\le v, y\le u\rightarrow u-y+v-x=(u+v)-(x+y)xv,yuuy+vx=(u+v)(x+y)
  • x≤v,y≥u→y−u+v−x=(v−u)+(y−x)x\le v,y\ge u\rightarrow y-u+v-x=(v-u)+(y-x)xv,yuyu+vx=(vu)+(yx)
  • x≥v,y≤u→u−y+x−v=(u−v)+(x−y)x\ge v,y\le u\rightarrow u-y+x-v=(u-v)+(x-y)xv,yuuy+xv=(uv)+(xy)
  • x≥v,y≥u→y−u+x−v=(x+y)−(u+v)x\ge v,y\ge u\rightarrow y-u+x-v=(x+y)-(u+v)xv,yuyu+xv=(x+y)(u+v)

其实l,rl,rl,r没有必要区分,也就是说case3;case4case\ 3;case\ 4case 3;case 4本质上是和case1;case2case\ 1;case\ 2case 1;case 2等价的

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 300005
#define int long long
int n, ans, sum;
int A[maxn];
vector < pair < int, int > > G;int Fabs( int x ) {return x < 0 ? -x : x;
}int work() {G.clear();for( int i = 1;i < n;i ++ )if( A[i + 1] >= A[i] ) G.push_back( make_pair( A[i], A[i + 1] ) );sort( G.begin(), G.end() );/*l belongs to G{second};r belongs to G{first} delta=|A(r+1)-A(l)|+|A(r)-A(l-1)|-|A(r+1)-A(r)|-|A(l)-A(l-1)|because of sorting --> A(r+1)>=A(r);A(l)>=A(l-1);A(r)>=A(l-1)delta=|A(r+1)-A(l)|+A(r)-A(l-1)-A(r+1)+A(r)-A(l)+A(l-1)delta=|A(r+1)-A(l)|+2*A(r)-A(r+1)-A(l)case 1:A(r+1)>=A(l)delta=A(r+1)-A(l)+2*A(r)-A(r+1)-A(l)=2*A(r)-2*A(l)case 2:A(l)>A(r+1)delta=-A(r+1)+A(l)+2*A(r)-A(r+1)-A(l)=2*A(r)-2*A(r+1)that meanswhat to plus is exactlt 2*A(r)what to sub is min(A(l),A(r+1))*2*/int ret = sum, t = 0;for( int i = 0;i < G.size();i ++ ) {ret = min( ret, sum + ( G[i].first << 1 ) - ( min( t, G[i].second ) << 1 ) );t = max( t, G[i].second );}return ret;
}signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &A[i] );for( int i = 1;i < n;i ++ )sum += Fabs( A[i + 1] - A[i] );ans = sum;for( int i = 1;i < n;i ++ )//l=1,r=i特殊情况判断ans = min( ans, sum + Fabs( A[i + 1] - A[1] ) - Fabs( A[i + 1] - A[i] ) );for( int i = 2;i <= n;i ++ )//l=i,r=n特殊情况判断ans = min( ans, sum + Fabs( A[n] - A[i - 1] ) - Fabs( A[i] - A[i - 1] ) );ans = min( ans, work() );reverse( A + 1, A + n + 1 );ans = min( ans, work() );printf( "%lld\n", ans );return 0;
}

2021-05-22(ABC 202)

A - Three Dice

连相对面筛子之和为777都告诉了

B - 180°

6996倒着输出

C - Made Up

差点被绕晕了

#include <cstdio>
#include <vector>
using namespace std;
#define maxn 100005
vector < int > g[maxn];
int a[maxn], b[maxn], c[maxn], tot[maxn], cnt[maxn];
int n;int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &a[i] ), tot[a[i]] ++;for( int i = 1;i <= n;i ++ )scanf( "%d", &b[i] ), g[b[i]].push_back( i );for( int i = 1;i <= n;i ++ ) {scanf( "%d", &c[i] );cnt[c[i]] ++;}long long ans = 0;for( int i = 1;i <= n;i ++ )for( int j = 0;j < g[i].size();j ++ )ans += 1ll * tot[i] * cnt[g[i][j]];printf( "%lld\n", ans );return 0;
}

D - aab aba baa

从高到低依次确定位置,记cntAcnt_AcntA为前面高位已确定的a个数,cntBcnt_BcntB为前面高位已确定的b个数

如果当前位填a,那么方案数就是在剩下位置中插入剩下a的个数Ci−1A−cntA−1C_{i-1}^{A-cnt_A-1}Ci1AcntA1,与kkk作比较即可

#include <cstdio>
#define int long long
int A, B, K;
int c[65][65];signed main() {scanf( "%lld %lld %lld", &A, &B, &K );for( int i = 0;i <= 60;i ++ ) {c[i][0] = c[i][i] = 1;for( int j = 1;j < i;j ++ )c[i][j] = c[i - 1][j] + c[i - 1][j - 1];}int cnt_A = 0, cnt_B = 0;for( int i = A + B;i;i -- )if( c[i - 1][A - cnt_A - 1] < K )printf( "b" ), K -= c[i - 1][A - cnt_A - 1], cnt_B ++;elseprintf( "a" ), cnt_A ++;return 0;
}

E - Count Descendants

距离要求一定恰好ddd,不妨按深度(到根的距离就是深度)分层

要求必须经过uuu,也就是说要在uuu的子树内进行对应深度点数的查找

这就不得不跟树的dfndfndfn序挂钩了

暴力手写两个二分找到uuu管辖的dfndfndfn序列,再暴力手写两个二分找到uuu子树内对应深度的左右点

不会vectorlower_bound简便查找,只能老实手写二分,然后复制粘贴哈哈哈哈

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200005
vector < int > G[maxn], D[maxn];
int n, Q, cnt;
int siz[maxn], dep[maxn], dfn[maxn];void dfs( int u ) {siz[u] = 1, dfn[u] = ++ cnt;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];dep[v] = dep[u] + 1;dfs( v );siz[u] += siz[v];}
}int main() {scanf( "%d", &n );for( int i = 2, x;i <= n;i ++ ) {scanf( "%d", &x );G[x].push_back( i );}dfs( 1 );for( int i = 1;i <= n;i ++ )D[dep[i]].push_back( dfn[i] );for( int i = 1;i <= n;i ++ )sort( D[i].begin(), D[i].end() );scanf( "%d", &Q );while( Q -- ) {int u, d;scanf( "%d %d", &u, &d );if( dep[u] > d || ! D[d].size() ) printf( "0\n" );else {int l = 0, r = D[dep[u]].size() - 1, pos_r = cnt + 1, pos_l = -1;while( l <= r ) {int mid = ( l + r ) >> 1;if( D[dep[u]][mid] > dfn[u] ) pos_r = D[dep[u]][mid], r = mid - 1;else l = mid + 1;}l = 0, r = D[dep[u]].size() - 1;while( l <= r ) {int mid = ( l + r ) >> 1;if( D[dep[u]][mid] <= dfn[u] ) pos_l = D[dep[u]][mid], l = mid + 1;else r = mid - 1;}l = 0, r = D[d].size() - 1;int L = -1, R = -1;while( l <= r ) {int mid = ( l + r ) >> 1;if( D[d][mid] >= pos_l ) L = mid, r = mid - 1;else l = mid + 1;}l = 0, r = D[d].size() - 1;while( l <= r ) {int mid = ( l + r ) >> 1;if( D[d][mid] < pos_r ) R = mid, l = mid + 1;else r = mid - 1; }if( ( ~ L ) && ( ~ R ) ) printf( "%d\n", R - L + 1 );else printf( "0\n" );}}return 0;
}

2021-03-28(ARC 116)

A - Odd vs Even

根据加法性质推出奇数乘奇数必为奇数(相当于奇数个奇数相加),偶数乘偶数/奇数乘偶数必为偶数

nnn如果是奇数,其所有因子均为奇数;nnn如果为偶数,必有部分因子含有因子222,继续分一定是偶数因子多的

特判一下是否恰好为因子222即可

B - Products of Min-Max

先排个序,一个数单独成列提前算,考虑枚举最小值,然后计算不同最大值的方案数(方案数自带最大值)

对于当前最小值所在序列位置iii,最大值若在j,i<jj,i<jj,i<j,则方案数为2j−i−12^{j-i-1}2ji1(中间的数可选可不选)

贡献加成buff2j−i−1×aj2^{j-i-1}\times a_j2ji1×aj,用后缀和优化

考虑i→i+1i\rightarrow i+1ii+1贡献转移,所有的方案数都需要/2/2/2。特别地,原来的[i,i+1][i,i+1][i,i+1]方案数只有111,其余都是222的倍数

所以不如从一开始就不把iii的下一位放进后缀和,单独处理,转移时也直接提出来

#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 200005
int n, ret, cnt;
int a[maxn], sum[maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans;
}signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &a[i] ), ret = ( ret + a[i] * a[i] % mod ) % mod;sort( a + 1, a + n + 1 );for( int i = n;i;i -- )sum[i] = sum[i + 1] + a[i];for( int i = 3;i <= n;i ++ )cnt = ( cnt + qkpow( 2, i - 2 ) * a[i] % mod ) % mod;for( int i = 1;i <= n;i ++ ) {ret = ( ret + a[i] * ( cnt + a[i + 1] ) % mod ) % mod;cnt = ( cnt * qkpow( 2, mod - 2 ) % mod - a[i + 2] + mod ) % mod;}printf( "%lld\n", ret );return 0;
}

C - Multiple Sequences

对于A1A_1A1最低翻两倍,也最多只能翻181818次左右到达MMM的上限。

每次翻倍的倍数可能不同,但是序列一定长成x,x,x...,kx,kx,kx...tkx,tkx,tkx...

所以我们只好奇一共变化了几次,并且变化点在哪里

dpi,j:An=idp_{i,j}:A_n=idpi,j:An=i一共翻了jjj次的方案数

最后枚举AnA_nAn以及变换次数,乘上变化点的方案数CnjC_n^jCnj

#include <cstdio>
#define int long long
#define mod 998244353
#define maxn 200005
int n, m;
int fac[maxn], inv[maxn];
int f[maxn][20];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans;
}int C( int n, int m ) {return fac[n] * inv[m] % mod * inv[n - m] % mod;
}signed main() {scanf( "%lld %lld", &n, &m );f[1][0] = 1;for( int i = 1;i <= m;i ++ )for( int j = 2;;j ++ ) {if( i * j > m ) break;else {for( int k = 0;k < 18;k ++ )f[i * j][k + 1] = ( f[i * j][k + 1] + f[i][k] ) % mod;}}int ans = 0;fac[0] = inv[0] = 1;for( int i = 1;i <= n;i ++ )fac[i] = fac[i - 1] * i % mod;inv[n] = qkpow( fac[n], mod - 2 );for( int i = n - 1;i;i -- )inv[i] = inv[i + 1] * ( i + 1 ) % mod;for( int i = 1;i <= m;i ++ )for( int k = 0;k <= 18;k ++ )ans = ( ans + f[i][k] * C( n, k ) % mod ) % mod;printf( "%lld\n", ans % mod );return 0;
}

D - I Wanna Win The Game

⨁i=1nAi=0→\bigoplus_{i=1}^nA_i=0\rightarrowi=1nAi=0 每个二进制位上所有数的111的个数为偶数个。

时间复杂度顿时嗅到了带logloglogDPDPDP内味儿

fi,j:f_{i,j}:fi,j: 到第iii位时数值和为jjj的方案数

转移枚举第iii位有多少个111,这一位和前面所有位的方案和都可以组合产生不同的数

fi,j×Cnk→fi+1,j+2kf_{i,j}\times C_{n}^{k}\rightarrow f_{i+1,j+2^k}fi,j×Cnkfi+1,j+2k

#include <cstdio>
#define int long long
#define mod 998244353
#define maxn 5005
#define Siz 15
int n, m;
int C[maxn][maxn], f[Siz + 2][maxn];signed main() {scanf( "%lld %lld", &n, &m );for( int i = 0;i < maxn;i ++ ) {C[i][0] = C[i][i] = 1;for( int j = 1;j < i;j ++ )C[i][j] = ( C[i - 1][j - 1] + C[i - 1][j] ) % mod;}f[0][0] = 1;for( int i = 0;i < Siz;i ++ )for( int j = 0;j <= m;j ++ )for( int k = 0;k <= n;k += 2 )if( j + ( 1ll << i ) * k > m )break;elsef[i + 1][j + ( 1 << i ) * k] = ( f[i + 1][j + ( 1 << i ) * k] + f[i][j] * C[n][k] % mod ) % mod;printf( "%lld\n", f[Siz][m] );return 0;
}

E - Spread of Information

贪心的构造:能尽可能往祖先上放点就尽可能往上放

因为如果关键点越往上,能覆盖的范围就越大,越有可能覆盖更多点

从上往下越往下越放,是不可以的,因为往下放的点是不能越过祖先去帮助旁系

在这里插入图片描述

如图:假设二分距离为midmidmid,从上往下放就会左右儿子各放一个(黄色),但是明明从下往上考虑一个midmidmid的关键点(红色)就可以解决旁系问题

dfsdfsdfs搜树,对于点uuu记录子树信息两种状态(含自己)

  • 子树内最远没有被解决的点的距离
  • 子树内最近的关键点还能延伸的距离

每个点都只会触发两个信息中的一个

当且仅当最远距离恰好为二分的限制距离时,才迫不得已放个关键点

注意儿子信息回传的时候,最远未解决点距离还要再+1+1+1/最近的关键点还能延伸的距离−1-11

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 200005
vector < int > G[maxn];
int n, k, cnt;int dfs( int u, int fa, int d ) {int t, unsolve_d = 0, free_d = 0;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;else t = dfs( v, u, d );if( t > 0 ) unsolve_d = max( unsolve_d, t );else free_d = max( free_d, -t );}free_d --;if( free_d >= unsolve_d ) return -free_d;else if( unsolve_d == d ) {cnt ++;return -d;}return unsolve_d + 1;
}bool check( int x ) {cnt = 0;if( dfs( 1, 0, x ) > 0 ) cnt ++; else;return cnt <= k;
}int main() {scanf( "%d %d", &n, &k );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}int l = 0, r = n, ans;while( l <= r ) {int mid = ( l + r ) >> 1;if( check( mid ) ) ans = mid, r = mid - 1;else l = mid + 1;}printf( "%d\n", ans );return 0;
}

2021-05-23(ARC 120)

A - Max Add

不难发现a1a_1a1加上最大值后,a2a_2a2一定加上a1+maxxa_1+maxxa1+maxxa3a_3a3一定加上a1+a2+maxx......a_1+a_2+maxx......a1+a2+maxx......

归纳得对于A[1...i]A[1...i]A[1...i],最大值增加iii次,A1A_1A1增加iii次,A2A_2A2增加i−1i-1i1..................

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
int n;signed main() {scanf( "%lld", &n );int maxx = 0, sum = 0, pre = 0;for( int i = 1, x;i <= n;i ++ ) {scanf( "%lld", &x );maxx = max( maxx, x );sum += x, pre += sum;printf( "%lld\n", maxx * i + pre );}return 0;
}

B - Uniformly Distributed

考虑一个2×22\times 22×2的方格,(i,j),(i,j+1),(i+1,j),(i+1,j+1)(i,j),(i,j+1),(i+1,j),(i+1,j+1)(i,j),(i,j+1),(i+1,j),(i+1,j+1)

(1,1)→(i,j),(i,j)→(i+1,j+1),(i+1,j+1)→(n,m)(1,1)\rightarrow (i,j),(i,j)\rightarrow (i+1,j+1),(i+1,j+1)\rightarrow (n,m)(1,1)(i,j),(i,j)(i+1,j+1),(i+1,j+1)(n,m)

  • (i,j)→(i+1,j)→(i+1,j+1)(i,j)\rightarrow (i+1,j)\rightarrow (i+1,j+1)(i,j)(i+1,j)(i+1,j+1)

  • (i,j)→(i,j+1)→(i+1,j+1)(i,j)\rightarrow (i,j+1)\rightarrow (i+1,j+1)(i,j)(i,j+1)(i+1,j+1)

两种方案仅取决于(i+1,j),(i,j+1)(i+1,j),(i,j+1)(i+1,j),(i,j+1)颜色是否相同

更一般的,有(i+j)(i+j)(i+j)一样的网格应该是同一颜色的

每次操作一定是行加一或者列加一,相当于把行列之和相等的方格数分类,每种方案都是在每类中选一个点

如果某类中两种颜色都出现,000

只出现一种颜色,111

全都是?222

#include <cstdio>
#define mod 998244353
#define maxn 1005
int n, m;
char ch[maxn][maxn];
bool B[maxn], R[maxn];int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ )scanf( "%s", ch[i] + 1 );int ans = 1;for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )if( ch[i][j] == '.' ) continue;else if( ch[i][j] == 'B' ) B[i + j] = 1;else R[i + j] = 1;for( int i = 2;i <= n + m;i ++ )if( B[i] && R[i] ) return ! printf( "0\n" );else if( ! B[i] && ! R[i] ) ans = 2ll * ans % mod;printf( "%d\n", ans );return 0;
}

C - Swaps 2

发现操作的性质,如果左移值加一,右移值减一,与B题一样的性质,i+Aii+A_ii+Ai是个定值

也就是说如果Ai→Bj⇔Ai+i=Bj+jA_i\rightarrow B_j\Leftrightarrow A_i+i=B_j+jAiBjAi+i=Bj+j,以此分类

对于每个BjB_jBj,找距离自己最近的AiA_iAi,进行变换,这种依次顺序肯定比打乱顺序更优

对于每个iii记录自己被强制交换了多少次,带上这个贡献进行i→ji\rightarrow jij的转移即可

#include <set>
#include <cstdio>
using namespace std;
#define maxn 200005
#define int long long
set < pair < int, int > > st;
set < pair < int, int > > :: iterator it;
int n;
int t[maxn];int lowbit( int x ) {return x & ( -x );
}void add( int x, int val ) {for( int i = x;i <= n;i += lowbit( i ) ) t[i] += val;
}int query( int x ) {int ans = 0;for( int i = x;i;i -= lowbit( i ) ) ans += t[i];return ans;
}signed main() {scanf( "%lld", &n );for( int i = 1, x;i <= n;i ++ ) {scanf( "%lld", &x );st.insert( make_pair( x + i, i ) );}bool flag = 0; int ans = 0;for( int i = 1, x;i <= n;i ++ ) {scanf( "%lld", &x );it = st.lower_bound( make_pair( x + i, 0 ) );if( it == st.end() ) flag = 1;else {int v = query( ( *it ).second );add( 1, 1 ), add( ( *it ).second, -1 );ans += ( *it ).second + v - i;st.erase( it );}}if( flag ) printf( "-1\n" );else printf( "%lld\n", ans );return 0;
}

D - Bracket Score 2

AiA_iAi反应到数轴上去,相当于匹配两个点,使得覆盖的线段的长度最大(重复线段长度重复计算)

对于第一个点和第二个点间的线段最多只能被覆盖一次,因为第二个点左边只有一个点的配对

第二个点和第三个点间的线段最多只能被覆盖两次,第一个点和第二个点的配对都经过第三个点才行

……有没有可能构造一种配对使得每条线段被覆盖次数都达到最大化

这显然是可以的,只需要第一个点和最后一个点,第二个点和倒数第二个点…配对

也就是说,按AAA排序,最小的和最大的配对()

但是得注意谁在前面谁在后面进行()的分配,模拟即可

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 400005
struct node {int val, id;
}A[maxn];
int n;
bool flag[maxn];bool cmp( node x, node y ) {return x.val == y.val ? x.id < y.id : x.val < y.val;
}int main() {scanf( "%d", &n );for( int i = 1;i <= ( n << 1 );i ++ )scanf( "%d", &A[i].val ), A[i].id = i;sort( A + 1, A + ( n << 1 ) + 1, cmp );for( int i = 1;i <= n;i ++ ) flag[A[i].id] = 1;int cnt = 0;for( int i = 1;i <= ( n << 1 );i ++ ) if( flag[i] ) {if( cnt < 0 ) printf( ")" );else printf( "(" );cnt ++;}else {if( cnt > 0 ) printf( ")" );else printf( "(" );cnt --;}return 0;
}

E - 1D Party

将移动对应到坐标系上,右上代表向右走,左上代表向左走,交点的高度即为所花费的时间

在这里插入图片描述

(i,i+1)(i,i+1)(i,i+1)先相遇的时候,不妨交换二者的任务,让iiii+1i+1i+1去与i+2i+2i+2会面,i+1i+1i+1iii去和i−1i-1i1会面

(相遇的两人重新换了“身份”)

也就是说,每个人的实际走位我们并不感冒,我们所在意的无非是围成的相交的三角形的最大高度

在这里插入图片描述

每个人的轨迹变成有且仅有一条射线,问题变成了求最大高度的最小值

上图明显可以通过改变轨迹围成的三角形更优

在这里插入图片描述

dpi:dp_i:dpi:iii个点都没问题的最大高度的最小值,看似需要枚举jjj

dpi=min⁡j=1i−2(max(dpj,ai−aj−12))dp_i=\min_{j=1}^{i-2}\bigg(max(dp_j,\frac{a_i-a_{j-1}}{2})\bigg)dpi=minj=1i2(max(dpj,2aiaj1))

因为每个点发出一条射线,所以满足条件的图形一定是相交的三角形(不包含共享端点的那种)

这是O(n2)O(n^2)O(n2)的转移,其实不然

真正我们所需要的无非是dpi−2dp_{i-2}dpi2(包含4个端点的三角形),dpi−3dp_{i-3}dpi3(包含5个端点的三角形)

dpi=min(max(dpi−2,ai−ai−32),max(dpi−3,ai−ai−42))dp_{i}=min\bigg(max(dp_{i-2},\frac{a_i-a_{i-3}}{2}),max(dp_{i-3},\frac{a_i-a_{i-4}}{2})\bigg)dpi=min(max(dpi2,2aiai3),max(dpi3,2aiai4))

因为三角形必须是相交的,所以中间的三角形自己有两个端点,包含左右三角形各自一个端点

只有可能是最开头和最末尾的三角形才有可能只包含三个端点

在这里插入图片描述

在这里插入图片描述

四个端点和五个端点显然所引发的交点并不完全一样,所以需要枚举

在这里插入图片描述

六个显然可以拆分成更小的可能(与四个端点或者五个端点的交点是一样的),七个甚至更多亦是如此

至于最开头和最结尾有可能为三个的三角形,可以通过加虚点变成“四个端点的三角形”,也可以特判处理

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
#define int long long
int n;
int A[maxn], dp[maxn];signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &A[i] );A[n + 1] = A[n], dp[2] = A[2] - A[1], dp[3] = A[3] - A[1];for( int i = 4;i <= n + 1;i ++ )dp[i] = min( max( dp[i - 2], A[i] - A[i - 3] ), max( dp[i - 3], A[i] - A[i - 4] ) );printf( "%lld\n", dp[n + 1] / 2 );return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317465.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

G - Tiling FZU - 2040(未解决)

G - Tiling FZU - 2040 题意&#xff1a; m * n的矩阵&#xff0c;通过任意大小的矩阵&#xff0c;有多少种填充方式&#xff1f; 图片为3 * 2的矩阵有如下填充方式&#xff1a; 题解&#xff1a; 矩阵快速幂 目前还没参透 代码:

I - The Mad Mathematician FZU - 2042(未解决)

I - The Mad Mathematician FZU - 2042 题意&#xff1a; 给你一段伪代码&#xff1a;如图 现在给你A&#xff0c;B&#xff0c;C&#xff0c;D&#xff0c;E&#xff0c;的具体值&#xff0c;问你sum是多少 0<A,B,C,D,E<263-1 题解&#xff1a; 数位dp&#xff1f;…

程序员修仙之路--高性能排序多个文件

点击上方蓝色字体&#xff0c;关注我们菜菜呀&#xff0c;昨天晚上班级空间崩溃了程序员主力 Y总what&#xff1f;菜菜我看服务器上写了很多个日志文件&#xff0c;我看着太费劲了&#xff0c;能不能按照日期排序整合成一个文件呀&#xff1f;程序员主力 Y总Y总要查日志呀&…

【.NET Core项目实战-统一认证平台】第十四章 授权篇-自定义授权方式

上篇文章我介绍了如何强制令牌过期的实现&#xff0c;相信大家对IdentityServer4的验证流程有了更深的了解&#xff0c;本篇我将介绍如何使用自定义的授权方式集成老的业务系统验证&#xff0c;然后根据不同的客户端使用不同的认证方式来集成到统一认证平台。.netcore项目实战交…

D - Covering HDU - 6185(未解决完)

D - Covering HDU - 6185 题意&#xff1a; 4 * n的地板&#xff0c;有无数个1 * 2 和2 * 1 的砖块&#xff0c;问有多少方式填满&#xff1f; 1≤n≤10^18 题解&#xff1a; 矩阵快速幂 代码&#xff1a;

.NET-记一次架构优化实战与方案-前端优化

前言上一篇《.NET-记一次架构优化实战与方案-梳理篇》整理了基本的业务知识&#xff0c;同时也罗列了存在的问题&#xff0c;本篇主要是针对任务列表的页面进行性能优化。该篇主要涉及的是代码实现上的优化&#xff0c;实现上的问题是战术债务&#xff0c;也就是我们平常出现的…

GDOI2022游记

文章目录Day -1Day 0Day 1Day 2Day 3()Day ?~?Day ?Day -1 考前好像写题状态不太好&#xff08;可能是纯粹的懒&#xff09;。 开始写板子&#xff0c;很多算法都很久没碰了&#xff0c;有的调了很久才过。树剖都调了一个多小时&#xff0c;身败名裂。不过想想省选应该不怎…

.NET-记一次架构优化实战与方案-梳理篇

前言程序员输出是他敲写的代码&#xff0c;那么输入就是他思考好的设计。因此不做设计是不存在&#xff0c;设计只分优秀的设计和糟糕的设计。为了避免过度设计浪费成本&#xff0c;需要针对现有业务与问题进行展开。业务梳理是不可避免的。优化是无止尽&#xff0c;为了更有成…

.NET Core 3.0 中的数据库驱动框架 System.Data

虽然没有得到很多关注&#xff0c;但System.Data对于.NET 中任何关系型数据库的访问都至关重要。因为其前身是 ActiveX Data Objects&#xff0c;所以它也被称为 ADO.NET。System.Data 提供了一个通用框架&#xff0c;是构建.NET 数据库驱动程序的基础。该框架提供了数据库驱动…

Matrix Problem

Matrix Problem 题意&#xff1a; 给你一个n * m的二维数据c&#xff0c;c的每个元素值为0或1 现在要求你构造同样大小的数组a和b&#xff0c;要求c[i][j] 1’的话&#xff0c;a[i][j] b[i][j] ‘1’&#xff0c;如果c[i][j] ‘0’.a[i][j]!b[i][j]&#xff0c;且a和b中的1…

ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

一、前言在涉及到后端项目的开发中&#xff0c;如何实现对于用户权限的管控是需要我们首先考虑的&#xff0c;在实际开发过程中&#xff0c;我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能&#xff0c;而在 Grapefruit.VuCore 这个项目中&#xff0c;我将使用 Jwt …

[2021-06-19] 提高组新手副本Ⅱ(联网,欧几里得,分解树,开关灯)

文章目录考试心路历程联网titlesolutioncode欧几里得titlesolutioncode分解树titlesolutioncode开关灯titlesolutioncode考试心路历程 佛了佛了&#xff0c;caocaocaocaocaocao 人直接炸嗨升天 并查集直接送走200200200分&#xff01;&#xff01;&#xff01;我屮艸芔茻 T1二…

在.NET Core中设计自己的服务宿主(Service Hosting)框架

很多时候我们都会有设计一个后台服务的需求&#xff0c;比如&#xff0c;传统的Windows Service&#xff0c;或者Linux下的守护进程。这类应用的一个共同特点就是后台运行&#xff0c;并且不占用控制台界面。通常情况下&#xff0c;后台服务在提供服务时&#xff0c;会通过日志…

CF1131 G. Most Dangerous Shark(DP+单调栈优化)

文章目录problemsolutioncodeproblem solution dpi:dp_i:dpi​: 前iii个多米诺骨牌全都倒下的最小花费 li,ril_i,r_ili​,ri​分别表示第iii个多米诺骨牌倒下时所能波及到的最左/右位置 往左倒&#xff0c;则[li,i)[l_i,i)[li​,i)内的牌都可以选择性地先推倒 dpimin⁡{dpjcos…

Cat Virus

Cat Virus 题意&#xff1a; 让你构造一颗树&#xff0c;要求如果一个点为黑&#xff0c;其子树全为黑&#xff0c;白点任意&#xff0c;现在让你构造一棵树&#xff0c;使其染色方案数为K&#xff0c;节点尽可能少 题解&#xff1a; 首先画出k<9的全部情况&#xff0c;并…

微软发布XAML Studio工具:快速构建UWP XAML原型

IT之家1月30日消息 微软车库的最新项目XAML Studio已经在Windows 10应用商店上架&#xff0c;将帮助开发人员快速构建UWP XAML原型&#xff0c;以后可以轻松地将其复制到Visual Studio中。它将允许开发人员实时预览他们的XAML代码&#xff0c;并与结果进行交互&#xff0c;就像…

.NET Core 3 Preview 2发布,C#8更强大的模式匹配

.NET Core 3 Preview 2 发布了&#xff0c;此版本主要带来了 C# 8 相关的新功能&#xff0c;C# 8 Preview 2 是 .NET Core 3 SDK 的一部分。C# 8 中使用模式进行更多操作&#xff0c;主要特性包括&#xff1a;using 声明改变需要缩进代码的方式&#xff0c;现在可以编写以下代码…

CF407 E. k-d-sequence(线段树+单调栈)

文章目录CF407 E. k-d-sequenceproblemsolutioncodeCF407 E. k-d-sequence problem solution special case&#xff0c;d0d0d0&#xff0c;相当于寻找最长的一段数字相同的区间 other case&#xff0c;如果要满足公差为ddd等差序列 区间内每个数在模ddd意义下同余每个数互不…

D. Binary Literature

D. Binary Literature 题意&#xff1a; 给三个长度为2 * n的01串&#xff0c;让你构造一个长度小于3 * n的字符串&#xff0c;使得这个串至少包含两个01串 题解&#xff1a; 很巧妙的构造题 三个指针分别指向三个串&#xff0c;因为是01串&#xff0c;所以一定存在两个字符…

安逸:鼠绘《诗与远方》

【作品名称】《诗与远方》【作者介绍】徐安&#xff08;笔名安逸&#xff0c;常州&#xff09;&#xff0c;PPT专家&#xff0c;鼠绘专家。平面设计专业&#xff0c;6年PPT设计经验&#xff1b;历届江苏省PPT制作大赛一等奖获得者&#xff0c;PA口袋动画重要合作人。PPT动画制作…