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

文章目录

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 , 1 0,1 0,1的个数都必须是偶数,且 s 1 , s n s_1,s_n s1,sn都必须为 1 1 1,这个可以用来判无解

1 1 1的个数为 c n t cnt cnt,前 c n t / 2 cnt/2 cnt/2 1 1 1都填左括号,后 c n t / 2 cnt/2 cnt/2个都填右括号

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

#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 × n n\times n n×n的国际象棋棋盘,黑格子都填 1 1 1,白格子都填 2 2 2 3 3 3就是来应急用的

颜色 1 1 1被禁用,就用颜色 2 2 2填白格子

颜色 2 2 2被禁用,就用颜色 1 1 1填黑格子

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

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

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

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

#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

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

先把 c i c_i ci提出来加起来, m a x ( c i , a j − a i ) = c i + m a x ( 0 , a j − a i − c i ) max(c_i,a_j-a_i)=c_i+max(0,a_j-a_i-c_i) max(ci,ajai)=ci+max(0,ajaici)

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

注意到如果 a j < a i a_j<a_i aj<ai,那么这次的坐车就一定是免费的

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

找一条从 a 1 a_1 a1(稳定度最小的城市)到 a n a_n an(稳定度最大的城市)的最短路,也就是额外的最小花费了

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

solution1

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

考虑重新编码整张图,跑 d i j k s t r a dijkstra dijkstra最短路(请不要忘记带上你可爱的堆优化)

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

所以要寻找一种优化建图

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

  • 找到最远的城市 j j j,满足 a j ≤ a i + c i a_j\le a_i+c_i ajai+ci,连边 0 0 0(城市 j j j一定是在 i i i的右边!)

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

  • j + 1 j+1 j+1连边 a j + 1 − a i − c i a_{j+1}-a_i-c_i aj+1aici j j j含义沿用上面一条

为哈只用跟 j + 1 j+1 j+1连,而考虑其他满足 a k − a i − c i > 0 a_k-a_i-c_i>0 akaici>0 k k k??

假设 j j j是距离 i i i最近的满足 a i + c i < a j a_i+c_i<a_j ai+ci<aj的城市, k k k是满足该条件的更远的城市

  • 路径选择 i − k i-k ik

    花费为 a k − a i − c i a_k-a_i-c_i akaici

  • 路径选择 i − j − k i-j-k ijk

    花费为 a j − a i − c i + a k − a j − c j = a k − a i − c i − c j a_j-a_i-c_i+a_k-a_j-c_j=a_k-a_i-c_i-c_j ajaici+akajcj=akaicicj

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

#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,最小化第一次到达 a i a_i ai的最小成本

对应为 ∑ i > 1 m a x ( 0 , a i − a j − c j )   { j < i } \sum_{i>1}max(0,a_i-a_j-c_j)\ \{j<i\} i>1max(0,aiajcj) {j<i},每一次更新前 i i i座城市各自 a + c a+c a+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 , 2 n ] [1,n],[n+1,2n] [1,n],[n+1,2n]

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

i , i ∈ [ 1 , n ] i,i∈[1,n] i,i[1,n]卡牌递增排序, f l a g i flag_i flagi表示该卡牌是否翻转了

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

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

用两个栈模拟维护 f f f递减

对于一个分界点 i i i,如果有 m i n ( f j , j ≤ i ) > m a x ( f j , 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];
			else
				return ! 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

假设红豆荚数目更少

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

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

#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),花费 x x x,再下走 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1),花费 y + 1 y+1 y+1

下走 ( x + 1 , y ) (x+1,y) (x+1,y),花费 y y y,再右走 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1),花费 x + 1 x+1 x+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

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

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

并且贡献就看人数取模 k k k余几,删除最差劲的最后几个,前缀和预处理就可以 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 ( n 2 ) 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)直线的斜率相同配对

建一个奇妙的图

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

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

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

#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

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

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

#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

结论:个数一定是 2 x / 4 x , x = k 2 , k ∈ Z 2x/4x,x=k^2,k∈Z 2x/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

贪心地想

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

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

#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 -1 1,右袜子 + 1 +1 +1,配对花费为 0 0 0

只花费 1 1 1的操作,不可能有花费 2 2 2的操作

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

  • 改变袜子颜色

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

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

    L , R L,R L,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;
			else
				if( 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

插入 D P DP DP/连续段 D P DP DP

#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 − 1 n-1 n1条道路要扣除的 x x x,就一定无解;反之一定有解

故此随便搞一棵生成树, d f s dfs dfs搜树

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

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

#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 → v u\rightarrow v uv存在一个长度为 l e n len len的路径,则 v → u v\rightarrow u vu一定存在一条长度为 − l e n -len len的路径

    v → u v\rightarrow u vu距离为 x x x,则可以选择从 v v v m o d − 1 mod-1 mod1 x x x l e n len len,最后还是停在 v v v,再走一次 x x x,到达 u u u

    则有: m o d ∗ x + ( m o d − 1 ) ∗ l e n ≡ − l e n mod*x+(mod-1)*len\equiv -len modx+(mod1)lenlen

  • u → u u\rightarrow u uu存在一条长度为 l e n len len的路径,则 u → u u\rightarrow u uu一定存在一条长度为 k ∗ l e n k*len klen的路径

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

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

    将环走恰好 m o d mod mod遍,模意义下的长度就是 0 0 0

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

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

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

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

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

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

所以每个点的环长集 S S S就等同于一个数 g g g的倍数集, g g g为所有环长的 g c d gcd gcd

枚举所有环长的 g c d gcd gcd,询问成立当且仅当 s + g s+g s+g的倍数是 t t t的倍数,转换成 s s s g c d ( 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 ∗ B A,A*(B-1),A*B A,A(B1),AB即可,如果 B = 2 B=2 B=2,后两个翻倍即可,一定要特判 B = 1 , N O B=1,NO B=1,NO

B. Nastia and a Good Array

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

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

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

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

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

不难知道,增加量非常小

#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 / 1 n-1/1 n1/1去判断

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

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

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

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

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

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

#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;
			}
			else
				p[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 × n n\times n n×n的矩阵,最多能填的个数为奇数列+偶数列的奇数行, ⌈ n 2 ⌉ × n + ( n − ⌈ n 2 ⌉ ) × ⌈ n 2 ⌉ \lceil\frac{n}{2}\rceil\times n+(n-\lceil\frac{n}{2}\rceil)\times \lceil\frac{n}{2}\rceil 2n×n+(n2n)×2n

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

所以需要满足 c n t = m a x { a i } , c n t ≤ ⌈ n 2 ⌉ × n cnt=max\{a_i\},cnt\le \lceil\frac{n}{2}\rceil\times n cnt=max{ai},cnt2n×n

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

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

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

#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;
		else
			l = 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

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

B. Minimal Cost

∀ i > 1 , a i = a i − 1   a n s = m i n ( a n s , v + m i n ( 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 , a i = a i − 1 + 1   a n s = m i n ( a n s , m i n ( 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 , a i > a i − 1 + 1   a n s = 0 \forall_{i>1,a_i>a_{i-1}+1}\ ans=0 i>1,ai>ai1+1 ans=0

C. Pekora and Trampoline

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

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

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

不妨对于每个点记录一下被前面点到达的次数,再与 S i S_i Si比较,多的次数全都往 i + 1 i+1 i+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 → \rightarrow 010...000

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

意味着每一位的前缀和 1 1 1的个数 u u u都要大于等于 v v v

#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

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

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

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

    当且仅当 i = 2 i=2 i=2时取等( i = 2 i=2 i=2时, F 1 = F 0 F_1=F_0 F1=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

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

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

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

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

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

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

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

上限是 n − 1 + ⌈ l o g 2 n ⌉ 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

一个 g c d gcd gcd完事

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 -x x的距离相向运动

#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

d p i , j dp_{i,j} dpi,j i i i个初始被占的位置使用了前 j j j个空位

#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

n n n用二进制表示,不难发现只有 n n n 1 1 1的最高位, k k k 0 0 0即可

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

B. Palindrome Game

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

  • 零的个数为 0 0 0

    什么也不用干, D R A W DRAW DRAW

  • 零的个数为 1 1 1

    A A A无法使用反转操作,支付代价, B O B BOB BOB

  • 零的个数为奇数

    A L I C E ALICE ALICE拥有必胜策略

    先花费 1 1 1占据最中间的 0 0 0,然后 B O B BOB BOB每次不管操作哪里的零, A L I C E ALICE ALICE都执行对称操作

    最后一次 B O B BOB BOB操作后,还剩下一个零,两人花费相同; A L I C E ALICE ALICE反转强制 B O B BOB BOB多花费 1 1 1

  • 零的个数为偶数

    B O B BOB BOB拥有必胜策略,类比 A L I C E ALICE ALICE策略

    一开始 A L I C E ALICE ALICE不能反转,只能花费 1 1 1 B O B BOB BOB执行对称花费

    最后一次 A L I C E ALICE ALICE花费, B O B BOB BOB反转强制 A L I C E ALICE ALICE再次花费,最后 A L I C E ALICE ALICE多花费 2 2 2

#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

  • 原串不为回文串

    结果一定是 A L I C E ALICE ALICE胜或者 D R A W DRAW DRAW

    • D R A W DRAW DRAW

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

      显然。 A L I C E ALICE ALICE如果不占领最中间字符, B O B BOB BOB就可以使用反转

    • A L I C E ALICE ALICE

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

      • 串是含有奇数个零的回文串, A L I C E ALICE ALICE拥有必胜策略

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

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

        B O B BOB BOB现在为先手,在此情况下先手必定会多支付 2 2 2

#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;
}

  • c n t 00 : s i = s n − i + 1 = 0 cnt_{00}:s_i=s_{n-i+1}=0 cnt00:si=sni+1=0的个数
  • c n t 01 : s i ≠ 0 cnt_{01}:s_i≠ 0 cnt01:si=0的个数
  • m i d = [ n & 1 ⋂ s m i d   p o s = 0 ] mid=[n\&1\bigcap s_{mid\ pos}=0] mid=[n&1smid pos=0]
  • r e v = [ l a s t   o p t   w a s   o p e r a t i o n   2 ] rev=[last\ opt\ was\ operation\ 2] rev=[last opt was operation 2]
  • d p c n t 00 , c n t 01 , m i d , r e v : m i n i m u m   c o s t   d i f f e r e n c e dp_{cnt_{00},cnt_{01},mid,rev}:minimum\ cost\ difference dpcnt00,cnt01,mid,rev:minimum cost difference

转移

  • r e v = 0 ⋂ c n t 01 > 0 rev=0\bigcap cnt_{01}>0 rev=0cnt01>0

    d p c n t 00 , c n t 01 , m i d , r e v = m i n { − d p c n t 00 , c n t 01 , m i d , 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}

  • c n t 00 > 0 , c n t 01 → c n t 00 cnt_{00}>0,cnt_{01}\rightarrow cnt_{00} cnt00>0,cnt01cnt00

    d p c n t 00 , c n t 01 , m i d , r e v = m i n { 1 − d p c n t 00 + 1 , c n t 01 − 1 , m i d , 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}

  • c n t 01 > 0 , c n t 00 → c n t 01 cnt_{01}>0,cnt_{00}\rightarrow cnt_{01} cnt01>0,cnt00cnt01

    d p c n t 00 , c n t 01 , m i d , r e v = m i n { 1 − d p c n t 00 + 1 , c n t 01 − 1 , m i d , 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}

  • m i d = 1 mid=1 mid=1

    d p c n t 00 , c n t 01 , m i d , r e v = m i n { 1 − d p c n t 00 + 1 , c n t 01 − 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}

结果

  • d p c n t 00 , c n t 01 , m i d , r e v > 0 : B O B dp_{cnt_{00},cnt_{01},mid,rev}>0:BOB dpcnt00,cnt01,mid,rev>0:BOB
  • d p c n t 00 , c n t 01 , m i d , r e v < 0 : A L I C E dp_{cnt_{00},cnt_{01},mid,rev}<0:ALICE dpcnt00,cnt01,mid,rev<0:ALICE
  • d p c n t 00 , c n t 01 , m i d , r e v = 0 : D R A W dp_{cnt_{00},cnt_{01},mid,rev}=0:DRAW dpcnt00,cnt01,mid,rev=0:DRAW

C. Sequence Pair Weight

考虑一对 ( i , j ) , { i < j , a i = a j } (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 , j i,j i,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

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

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

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

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

细节看代码比较好一点

在这里插入图片描述

图一

在这里插入图片描述

图二

#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

d p i , k : d i v i d e   [ 1 , i ]   i n t o   k   p a r t s   m i n i m u m   c o s t dp_{i,k}:divide\ [1,i]\ into\ k\ parts\ minimum\ cost dpi,k:divide [1,i] into k parts minimum cost

d p i , k = m i n j < i ( d p j , 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))

t h e   k e y   p o i n t   i s   h o w   t o   w o r k   o u t   w ( 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)

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

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

p r e i pre_i prei表示与 i i i颜色相同的前驱位置

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

  • j ∈ [ 1 , p r e i ) j∈[1,pre_i) j[1,prei)时, a i a_i ai最后出现的位置 p r e i → i , Δ = i − p r e i pre_i\rightarrow i,\Delta= i-pre_i preii,Δ=iprei

区间 f f f整体增加 Δ \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),那么最多只会增加一个( m e x mex mex不变)

否则则是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哥的排序不等式

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

#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

A 1 < A 2 < A 3 < . . . < A k > A k + 1 > A k + 2 > . . . A k − 1 A_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

贪心地有,只可能选 l v / r v l_v/r_v lv/rv

d p i , 0 / 1 dp_{i,0/1} dpi,0/1表示点 i i i选择 l i l_i li还是 r i r_i ri

D P DP DP即可

#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

d p n : 2 n dp_n:2n dpn:2n个点的方案数

如果 1 ↔ n + 1 ; 2 ↔ n + 2 ; . . . ; n ↔ 2 n 1\leftrightarrow n+1;2\leftrightarrow n+2;...;n\leftrightarrow 2n 1n+1;2n+2;...;n2n,点数全部使用完,相当于子任务的 d p 0 dp_0 dp0

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

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

所以是个前缀和

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

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

#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树上的管辖区间不交

判断是否是祖先关系,可以用 d f n dfn dfn序列管辖范围 l u ≤ l v ⋂ r v ≤ r u l_u\le l_v\bigcap r_v\le r_u lulvrvru进行判断。对keshi d f s dfs dfs

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

再根据keshi的管辖范围进行点的选择,用 s e t set set维护

欧拉序区间只存在包含或者不交的关系,如果有包含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与此题出入在于在城市等待炮车转向的时间

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

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

#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 2 2个点拥有 ≥ 2 \ge 2 2入边,则一定能找到一对点对没有公共朋友(不合法)

将最后答案的树分为三类

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

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

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

f i : f_i: fi: 深度为 i i i(相当于最长路径为 i i i)的满足条件的树方案数, p r e i = ∑ j = 1 i f j pre_i=\sum_{j=1}^if_j prei=j=1ifj

f i = f i − 1 + f i − 1 × p r e i − 2 + f i − 1 × ( f i − 1 + 1 ) 2 f_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)

  • 只有一个儿子

    f i − 1 f_{i-1} fi1

  • 有两个儿子

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

      f i − 1 × p r e i − 2 f_{i-1}\times pre_{i-2} fi1×prei2

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

      s o n 1 : 1 ↔ s o n 2 : 1 son_1:1 \leftrightarrow son_2:1 son1:1son2:1

      s o n 1 : 2 ↔ s o n 2 : 1 , 2 son_1:2 \leftrightarrow son_2:1,2 son1:2son2:1,2

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

      s o n 1 : f i ↔ s o n 2 : 1 , 2 , 3 , 4 , . . . , f i son_1:f_i \leftrightarrow son_2:1,2,3,4,...,f_i son1:fison2:1,2,3,4,...,fi

      等差数列求和

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

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

可以得出,一二类的答案
a n s 1 = 2 ( f n + f n − 1 ( f n − 1 + 1 ) p r e n − 2 2 + f n − 1 p r e n − 2 ( p r e n − 2 + 1 ) 2 + f n − 1 ( f n − 1 + 1 ) ( f n − 1 + 2 ) 6 ) − 1 ans_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 / 2 1/2 1/2个儿子的情况

    f n f_n fn

  • 3 3 3个儿子

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

      f n − 1 ( f n 1 + 1 ) 2 ⋅ p r e n − 2 \frac{f_{n-1}(f_{n_1}+1)}{2}·pre_{n-2} 2fn1(fn1+1)pren2

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

      f n − 1 ⋅ p r e n − 1 ( p r e n − 1 + 1 ) 2 f_{n-1}·\frac{pre_{n-1}(pre_{n-1}+1)}{2} fn12pren1(pren1+1)

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

      f n − 1 ( f n − 1 + 1 ) ( f n − 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 -1 1是减去同构的单链(方向不同的单链也是同构的)

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

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

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

a n s 2 = ∑ i = 0 n − 1 ( f i − 1 ) g n − i − 1 ans_2=\sum_{i=0}^{n-1}(f_i-1)g_{n-i-1} ans2=i=0n1(fi1)gni1

每个 f i f_i fi − 1 -1 1是为了减去单链的情况

最终答案为 a n s 1 + a n s 2 ans_1+ans_2 ans1+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......看似是多个的 1 1 1最后都能被 11 , 111 11,111 11,111凑出来

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

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

C. Potions

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

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

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

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

最大反悔贪心

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

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-ai
			printf( "1 %d %d\n", i, i + 1 );//ai,aj-ai -> aj,aj-ai
			printf( "1 %d %d\n", i, i + 1 );//aj,aj-ai -> aj+aj-ai,aj-ai
			printf( "2 %d %d\n", i, i + 1 );//aj+aj-ai,aj-ai -> aj+aj-ai,-aj
			printf( "1 %d %d\n", i, i + 1 );//aj+aj-ai,-aj -> aj-ai,-aj
			printf( "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

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

随机数选取一个人,假定这个人一定是被最后答案所包含的,那么最后答案的货币就是这个人喜爱的货币子集,选一个人不在最后答案范围的概率是 1 2 \frac{1}{2} 21,抽取五十次,还抽不到概率就是 1 2 50 \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 ( n u m b e r   o f   ′ 1 ′ ) = E ( o p t i o n ) = E ( o p t i o n   b e f o r e   s t o p ) + 1 E(number\ of\ '1')=E(option)=E(option\ before\ stop)+1 E(number of 1)=E(option)=E(option before stop)+1

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

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

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

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

1 n ⋅ 1 n − 1 ⋅ 1 n − 2 ⋅ ⋅ ⋅ 1 n − 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)!

a n s = ∑ i = 1 n C n − ( 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)!

最后加上 1 1 1即可

#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

首先地标按时间排序,就可以 D P DP DP

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

很不幸的,状态数高达 2 n m 2 2^nm^2 2nm2,已经是 1 e 8 1e8 1e8的级别了

observation

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

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

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

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

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

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

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

    [ f s , i + w s , j ≤ t j ] i + 1 → g s , 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

  • 从地标 j j j到传送门 i i i

    t j + m i n ( d i s j , i , w s , i ) → f s ∣ ( 1 < < i ) , g s , j t_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

  • 从地标 j j j到新地标 i i i

    [ m i n ( w s , i , d i s i , j ) + t j ≤ t i ] g s , j + 1 → g s , 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 ++ )//传送塔到新地标j
					if( 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到下一个地标i
					if( 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

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

B - Electric Board

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

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

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

#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

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

#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)开始清空 i i i行,那么第 i i i行中其它的红色就自然是清空列,然后那些列上的其它红色又清空各自行…

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

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

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

最后白色的个数就是 ( n − C n t R o w ) ∗ ( m − C n t C o l ) (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],就相当于答案更新 ∣ A r − A l − 1 ∣ + ∣ A r + 1 − A l ∣ − ∣ A r + 1 − A r ∣ − ∣ A l − A l − 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 , r l,r l,r有关,是个定值

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

暴力讨论一下,令 x = A l , y = A r , u = A l − 1 , v = A r + 1 x=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 , r l,r l,r没有必要区分,也就是说 c a s e   3 ; c a s e   4 case\ 3;case\ 4 case 3;case 4本质上是和 c a s e   1 ; c a s e   2 case\ 1;case\ 2 case 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 means
	what 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

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

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

从高到低依次确定位置,记 c n t A cnt_A cntA为前面高位已确定的a个数, c n t B cnt_B cntB为前面高位已确定的b个数

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

#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 ++;
		else
			printf( "a" ), cnt_A ++;
	return 0;
}

E - Count Descendants

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

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

这就不得不跟树的 d f n dfn dfn序挂钩了

暴力手写两个二分找到 u u u管辖的 d f n dfn dfn序列,再暴力手写两个二分找到 u u u子树内对应深度的左右点

不会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

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

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

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

B - Products of Min-Max

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

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

贡献加成buff 2 j − i − 1 × a j 2^{j-i-1}\times a_j 2ji1×aj,用后缀和优化

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

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

#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

对于 A 1 A_1 A1最低翻两倍,也最多只能翻 18 18 18次左右到达 M M M的上限。

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

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

d p i , j : A n = i dp_{i,j}:A_n=i dpi,j:An=i一共翻了 j j j次的方案数

最后枚举 A n A_n An以及变换次数,乘上变化点的方案数 C n j C_n^j Cnj

#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 = 1 n A i = 0 → \bigoplus_{i=1}^nA_i=0\rightarrow i=1nAi=0 每个二进制位上所有数的 1 1 1的个数为偶数个。

时间复杂度顿时嗅到了带 l o g log log D P DP DP内味儿

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

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

f i , j × C n k → f i + 1 , j + 2 k f_{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;
				else
					f[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

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

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

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

在这里插入图片描述

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

d f s dfs dfs搜树,对于点 u u u记录子树信息两种状态(含自己)

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

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

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

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

#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

不难发现 a 1 a_1 a1加上最大值后, a 2 a_2 a2一定加上 a 1 + m a x x a_1+maxx a1+maxx a 3 a_3 a3一定加上 a 1 + a 2 + m a x x . . . . . . a_1+a_2+maxx...... a1+a2+maxx......

归纳得对于 A [ 1... i ] A[1...i] A[1...i],最大值增加 i i i次, A 1 A_1 A1增加 i i i次, A 2 A_2 A2增加 i − 1 i-1 i1 . . . . . . ...... ......

#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 × 2 2\times 2 2×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)一样的网格应该是同一颜色的

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

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

只出现一种颜色, 1 1 1

全都是? 2 2 2

#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 + A i i+A_i i+Ai是个定值

也就是说如果 A i → B j ⇔ A i + i = B j + j A_i\rightarrow B_j\Leftrightarrow A_i+i=B_j+j AiBjAi+i=Bj+j,以此分类

对于每个 B j B_j Bj,找距离自己最近的 A i A_i Ai,进行变换,这种依次顺序肯定比打乱顺序更优

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

#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

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

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

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

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

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

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

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

#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)先相遇的时候,不妨交换二者的任务,让 i i i i + 1 i+1 i+1去与 i + 2 i+2 i+2会面, i + 1 i+1 i+1 i i i去和 i − 1 i-1 i1会面

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

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

在这里插入图片描述

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

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

在这里插入图片描述

d p i : dp_i: dpi: i i i个点都没问题的最大高度的最小值,看似需要枚举 j j j

d p i = min ⁡ j = 1 i − 2 ( m a x ( d p j , a i − a j − 1 2 ) ) 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 ( n 2 ) O(n^2) O(n2)的转移,其实不然

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

d p i = m i n ( m a x ( d p i − 2 , a i − a i − 3 2 ) , m a x ( d p i − 3 , a i − a i − 4 2 ) ) 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值