[CF/AT/Luogu]各大网站网赛 爆肝部部长工作报告文件Ⅱ


红色文件

  • ARC 122 F

  • LATOKEN-Round-1(Div.1+Div.2) F2,G,H

  • #726-Div.2 F

  • #727 E,F

  • #698 C,E,F

  • GR-12 G,H1,H2

明显地,CF/AT简单题也变得很难敲了o(╥﹏╥)o哭唧唧

CodeForces

LATOKEN-Round-1(Div.1+Div.2)

A. Colour the Flag

直接 b f s bfs bfs一层层确定颜色,判断是否冲突即可,注意整张图都是.的情况

#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 55
queue < pair < int, int > > q;
int T, n, m;
char g[maxn][maxn], ans[maxn][maxn];

bool inside( int x, int y ) {
	if( x < 1 || y < 1 || x > n || y > m ) return 0;
	else return 1;
}

bool check( int x, int y, char c ) {
	if( ! inside( x, y ) ) return 0;
	if( ans[x][y] == '.' ) {
		if( c == 'W' ) ans[x][y] = 'R';
		else ans[x][y] = 'W';
		q.push( make_pair( x, y ) );
		return 0;
	}
	if( ans[x][y] == c ) return 1;
	else return 0;
}

int main() {
	scanf( "%d", &T );
	again :
	while( T -- ) {
		while( ! q.empty() ) q.pop();
		scanf( "%d %d", &n, &m );
		for( int i = 1;i <= n;i ++ )
			scanf( "%s", g[i] + 1 );
		for( int i = 1;i <= n;i ++ )
			for( int j = 1;j <= m;j ++ )
				if( g[i][j] != '.' ) {
					q.push( make_pair( i, j ) );
					ans[i][j] = g[i][j];
				}
				else ans[i][j] = '.';
		if( q.empty() ) {
			ans[1][1] = 'R';
			q.push( make_pair( 1, 1 ) );
		}
		while( ! q.empty() ) {
			int x = q.front().first, y = q.front().second; q.pop();
			if( check( x - 1, y, ans[x][y] ) || check( x + 1, y, ans[x][y] ) || check( x, y - 1, ans[x][y] ) || check( x, y + 1, ans[x][y] ) ) {
				printf( "NO\n" );
				goto again;
			}
		}
		printf( "YES\n" );
		for( int i = 1;i <= n;i ++ ) {
			for( int j = 1;j <= m;j ++ )
				printf( "%c", ans[i][j] );
			printf( "\n" );
		}
	}
	return 0;
}

B. Histogram Ugliness

不难发现,对于相邻的 i , i + 1 i,i+1 i,i+1,如果高度不同,那么操作消更高一个一定会是答案变小

对于相邻的 i − 1 , i , i + 1 i-1,i,i+1 i1,i,i+1而言,假设最高的是 i i i,那么消到 i − 1 , i + 1 i-1,i+1 i1,i+1两者较高高度时答案最小

再往下消答案不变,所以直接扫一遍找中间 i i i是最高进行消即可

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 400005
int T, n;
int a[maxn];

int Fabs( int x ) {
	return x < 0 ? -x : x;
}

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		a[n + 1] = 0;
		for( int i = 1;i <= n;i ++ )
			scanf( "%lld", &a[i] );
		int ans = 0;
		for( int i = 1;i <= n;i ++ ) {
			int l = a[i - 1], r = a[i + 1];
			if( a[i] > l && a[i] > r ) {
				int maxh = max( l, r );
				ans += a[i] - maxh;
				a[i] = maxh;
			}
		}
		for( int i = 1;i <= n + 1;i ++ )
			ans += Fabs( a[i] - a[i - 1] );
		printf( "%lld\n", ans );
	}
	return 0;
}

C. Little Alawn’s Puzzle

因为两个都是排列,所以当交换 ( i , j ) (i,j) (i,j)后就必须不断的交换重复数字来抵消影响

其实是找环的个数( s i s_i si通过与 t t t的映射最后回到 s i s_i si的路径条数)

答案为 2 c n t 2^{cnt} 2cnt

#include <cstdio>
#define mod 1000000007
#define int long long
#define maxn 400005
int T, n;
int s[maxn], t[maxn], g[maxn];
bool vis[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", &T );
	again :
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1;i <= n;i ++ )
			vis[i] = 0, scanf( "%lld", &s[i] );
		for( int i = 1;i <= n;i ++ )
			scanf( "%lld", &t[i] );
		for( int i = 1;i <= n;i ++ )
			if( s[i] == t[i] ) {
				printf( "0\n" );
				goto again;
			}
			else g[s[i]] = t[i];
		int cnt = 0;
		for( int i = 1;i <= n;i ++ ) {
			if( vis[s[i]] ) continue;
			cnt ++;
			int now = s[i], flag = 1;
			while( flag || now != s[i] ) {
				vis[now] = 1;
				now = g[now];
				flag = 0;
			}
		}
		printf( "%lld\n", qkpow( 2, cnt ) );
	}
	return 0;
}

D. Lost Tree

显然,不管询问哪一个点,一定有点与之距离为 1 1 1(有直接树边相连)

先问一次1,把整棵树按距离分层建起来,再按奇偶分

哪边含的点少问哪边(问一层里的所有点,就把相邻两层的信息点找完了)

这样上线就能卡满 ⌈ n 2 ⌉ \lceil\frac{n}{2}\rceil 2n

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 2005
vector < int > g[2];
vector < pair < int, int > > ans;
int n;
int d[maxn];
bool vis[maxn][maxn];

void ask( int x ) {
	printf( "? %d\n", x );
	fflush( stdout );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &d[i] );
}

int main() {
	scanf( "%d", &n );
	ask( 1 );
	for( int i = 2;i <= n;i ++ )
		g[d[i] & 1].push_back( i );
	if( g[0].size() > g[1].size() ) swap( g[0], g[1] );
	for( int i = 1;i <= n;i ++ )
		if( d[i] == 1 ) ans.push_back( make_pair( 1, i ) ), vis[1][i] = vis[i][1] = 1;
	for( int i = 0;i < g[0].size();i ++ ) {
		ask( g[0][i] );
		for( int j = 1;j <= n;j ++ )
			if( d[j] == 1 ) {
				if( vis[g[0][i]][j] ) continue;
				else {
					vis[g[0][i]][j] = vis[j][g[0][i]] = 1;
					ans.push_back( make_pair( g[0][i], j ) );
				}
			}
	}
	printf( "!\n" );
	for( int i = 0;i < ans.size();i ++ )
		printf( "%d %d\n", ans[i].first, ans[i].second );
	return 0;
}

E. Lost Array

求整个序列的异或和 ⇔ \Leftrightarrow 每个数的操作次数为奇数1-0-1-0-1...

两种方法,bfs最短路/dp(本质是一样的)

f i : f_i: fi: 到目前操作为止一共有 i i i个数的值没有被抵消

枚举下一次有 j j j个数重复(这 j j j个数将变成没有贡献,而多了 k − j k-j kj个数的异或和)

最后 f n f_n fn即为最小操作次数

记录下每次最优转移的前驱以及重复个数,搜索还原即可

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 505
#define inf 0x3f3f3f3f
queue < int > q;
int n, k, ans, ret;
int f[maxn], pre[maxn], val[maxn];
bool vis[maxn];

void dfs( int now ) {
	if( ~ pre[now] ) dfs( pre[now] );
	else return;
	//cnt1:appeared
	//cnt2:disappeared
	printf( "?" );
	for( int cnt1 = val[now], cnt2 = k - val[now], i = 1;cnt1 || cnt2;i ++ ) {
		if( vis[i] ) {
			if( cnt1 ) vis[i] ^= 1, printf( " %d", i ), cnt1 --;
			else;
		} else {
			if( cnt2 ) vis[i] ^= 1, printf( " %d", i ), cnt2 --;
			else;
		}
	}
	printf( "\n" );
	fflush( stdout );
	scanf( "%d", &ans );
	ret ^= ans;
}

int main() {
	memset( pre, -1, sizeof( pre ) );
	scanf( "%d %d", &n, &k );
	memset( f, 0x3f, sizeof( f ) );
	f[0] = 0;
	q.push( 0 );
	while( ! q.empty() ) {
		int used = q.front(); q.pop();
		//used: how many numbers have contributed
		for( int i = 0;i <= k;i ++ ) {//next query has tot in i numbers same to used
			if( i <= used && k - i <= n - used ) {//k<=n-used+i
				int nxt = used - i + k - i;
				if( nxt > 0 && nxt <= n && f[used] + 1 < f[nxt] ) {
					f[nxt] = f[used] + 1;
					val[nxt] = i;
					pre[nxt] = used;
					q.push( nxt );
				}
			}
		}
	}
	if( f[n] == inf ) return ! printf( "-1\n" );
	else dfs( n );
	printf( "! %d\n", ret );
	return 0;
}

F1. Falling Sand (Easy Version)

把所有土块移出网格

把自动土块能干扰的土块连有向边,这样的暴力建图是 n 2 n^2 n2

事实上只需要知道上下左右最先干扰的土块,然后影响传递即可,建图锐减至 4 n 4n 4n

图中可能包含强联通图(其中一个松动,最后都会出局),缩点tarjan

那么最后其实就是找入度为 0 0 0的土块松动(没有土块能触发它们)

PS:简单版保证 a i a_i ai等于该列土块数量,所以俺干脆直接不读了

#include <map>
#include <stack>
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 400005
stack < int > sta;
vector < int > G[maxn], mp[maxn], id[maxn];
int n, m, cnt, Cnt, tot;
char s[maxn];
int dfn[maxn], low[maxn], last[maxn], scc[maxn], d[maxn];

void addEdge( int u, int v ) {
	G[u].push_back( v ); 
}

void tarjan( int u ) {
	dfn[u] = low[u] = ++ Cnt;
	sta.push( u );
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		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] ) {
		int v; ++ tot;
		do {
			v = sta.top(); sta.pop();
			scc[v] = tot;
		} while( v != u );
	}
}

int main() {
	scanf( "%d %d", &n, &m );
	mp[0].resize( m + 2 ), id[0].resize( m + 2 );
	for( int i = 1;i <= n;i ++ ) {
		mp[i].resize( m + 2 ), id[i].resize( m + 2 );
		scanf( "%s", s + 1 );
		for( int j = 1;j <= m;j ++ )
			mp[i][j] = s[j], id[i][j] = ++ cnt;
	}
	for( int i = n;i;i -- ) {
		for( int j = 1;j <= m;j ++ ) {
			if( mp[i][j] == '.' ) continue;
			if( mp[i - 1][j] == '#' ) addEdge( id[i][j], id[i - 1][j] );
			if( last[j] ) addEdge( id[i][j], last[j] );
			if( mp[i][j - 1] == '#' ) addEdge( id[i][j], id[i][j - 1] );
			else if( last[j - 1] ) addEdge( id[i][j], last[j - 1] );
			if( mp[i][j + 1] == '#' ) addEdge( id[i][j], id[i][j + 1] );
			else if( last[j + 1] ) addEdge( id[i][j], last[j + 1] );
		}
		for( int j = 1;j <= m;j ++ )
			if( mp[i][j] == '#' ) last[j] = id[i][j];
	}
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= m;j ++ )
			if( mp[i][j] == '#' && ! dfn[id[i][j]] ) tarjan( id[i][j] );
	for( int i = 1;i <= cnt;i ++ )
		for( int j = 0;j < G[i].size();j ++ )
			if( scc[i] ^ scc[G[i][j]] ) ++ d[scc[G[i][j]]];
	int ans = 0;
	for( int i = 1;i <= tot;i ++ )
		ans += ( d[i] == 0 );
	printf( "%d\n", ans );
	return 0;
} 

#726-Div.2

A. Arithmetic Array

总和如果比 n n n大,意味着需要不断补 0 0 0位置,sum-n

反之,只需要加一个正整数,1

B. Bad Boy

最远的两端(1,1)(n,m)

C. Challenging Cliffs

排序后直接找差距最小的两个(有多对,任选一对即可)然后按高度,比第一座高的按高度递增排列,最高的后面肯定接的是最矮的(出去两个标志建筑)没选的矮的递增排列,形成一个斜Z

D. Deleting Divisors

结论先猜后证yyds

  • case1: n = 2 k + 1 n=2k+1 n=2k+1 奇数 Bob
  • n = 2 k n=2k n=2k 偶数
    • case2: n = 2 t n=2^t n=2t 2 2 2的幂 t=2T+1->Bob t=2T->Alice
    • case3: n = 2 t × ( 2 p + 1 ) n=2^t\times (2p+1) n=2t×(2p+1) 2 2 2的幂 Alice

case1

减去一个奇数除数(因为所有的除数都是奇数),得到一个不是 2 2 2的幂的偶数

如果 d d d n n n的除数,那么 n − d n−d nd也必须能被 d d d整除,因为 d d d是奇数, n − d n−d nd不是 2 2 2的幂

case3

减去这个奇数除数,我们将得到 n − d n−d nd是奇数

要么对方拿到的奇数是质数——直接失败;要么就是case1操作后还一个不是 2 2 2的幂的偶数

所以总是不败的,因此这种局面先手必胜;故此可以退出case1奇数局面先手必败

case2

要么减半 n n n要么使 n n n成为不是 2 2 2的幂的偶数(已经证明了这是另一个玩家必胜的状态)

唯一的方法就只能是将 n n n减半,使其成为 2 2 2的另一个幂,知道有一个拿到 2 2 2,质数输掉比赛

所以局面跟 log ⁡ 2 n \log_2n log2n的奇偶挂钩

E. Erase and Extend

对于简单版本,直接暴力填充完所有位置,然后分情况

如果 s 1 < s i s_1<s_i s1<si那么从这里开始以 s 1 , i − 1 s_{1,i-1} s1,i1为周期直接重新填充

如果 s 1 = s i s_1=s_i s1=si就从 i i i开始以长度为 i − 1 i-1 i1 s 1 , i − 1 s_{1,i-1} s1,i1一一对比,如果更小,也要选择重新填充

#include <cstdio>
#define maxn 10005
int n, k;
char s[maxn];

int main() {
	scanf( "%d %d %s", &n, &k, s + 1 );
	while( n < k ) {
		for( int i = 1;i <= n;i ++ )
			s[i + n] = s[i];
		n <<= 1;
	}
	int now = 1;
	for( int i = 2;i <= k;i ++ ) {
		if( s[now] > s[i] ) continue;
		else if( s[now] < s[i] ) {
			int t = 1;
			for( int j = i;j <= k;j ++ ) {
				s[j] = s[t];
				t ++;
				if( t == i ) t = 1;
			}
		}
		else {
			bool flag = 0;
			for( int j = i;j <= ( i - 1 << 1 );j ++ )
				if( s[j - i + 1] < s[j] ) {
					flag = 1;
					break;
				}
				else if( s[j - i + 1] > s[j] ) break;
			if( flag ) {
				int t = 1;
				for( int j = i;j <= k;j ++ ) {
					s[j] = s[t];
					t ++;
					if( t == i ) t = 1;
				}	
			}	
		}
	}
	for( int i = 1;i <= k;i ++ )
		printf( "%c", s[i] );
	return 0;
}

困难版本 比简单版本还好敲 不能暴力的 O ( n k ) O(nk) O(nk)走人

用另外的数组存模板串,最后的答案为模板串的周期循环

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 500005
int n, k;
char s[maxn];

int main() {
	scanf( "%d %d %s", &n, &k, s + 1 );
	int len = 1;
	for( int i = 2;i <= min( n, k );i ++ ) {
		int pos = ( i - 1 ) % len + 1;
		if( s[pos] > s[i] ) len = i;
		else if( s[pos] < s[i] ) break;
	}
	for( int i = 1;i <= k;i ++ )
		printf( "%c", s[( i - 1 ) % len + 1] );
	return 0;
} 

#698-Div.2

A. Nezzar and Colorful Balls

求出现次数最多的数,输入还保证递增,不说了

B. Nezzar and Lucky Number

x x x分裂成几个 d d d,发现在 d d d前面加的数都是 10 10 10的倍数

我就想能不能把 x x x的个位卡掉后,剩下的 10 10 10的倍数就随便塞到分裂的 d d d前面

这就转化为了同余问题

#include <cstdio>
int T, Q, d, x;

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d %d", &Q, &d );
		while( Q -- ) {
			scanf( "%d", &x );
			int i;
			for( i = 1;i < 10;i ++ )
				if( ( d * i ) % 10 == x % 10 ) break;
			if( i * d > x ) printf( "NO\n" );
			else printf( "YES\n" );
		}
	}
	return 0;
}

D. Nezzar and Board

注意到 2 x − y 2x-y 2xy这个操作翻译成数学意思,其实是在数轴上 y y y关于 x x x的对称点

操作可以无限下去,到最后肯定是两点之间的距离相等为止,否则一定会再次操作定位新的坐标点

而这个距离就是原数组二者之间的 g c d gcd gcd

这道题很需要数学的直觉把我是SB再见

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 200005
int T, n, k;
int MS[maxn];

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", &T );
	while( T -- ) {
		scanf( "%lld %lld", &n, &k );
		for( int i = 1;i <= n;i ++ )
			scanf( "%lld", &MS[i] );
		sort( MS + 1, MS + n + 1 );
		int g = MS[2] - MS[1];
		for( int i = 3;i <= n;i ++ )
			g = gcd( g, MS[i] - MS[i - 1] );
		if( ( k - MS[1] ) % g ) printf( "NO\n" );
		else printf( "YES\n" );
	}	
	return 0;
}

GlobalRound-12

A. Avoid Trygub

直接字母排序输出

B. Balls of Steel

发现结果只有可能是1/-1 ,如果第一次两个点无法合并那么后面就不可能通过中转点合并,因为点的位置会发生改变,样例三就是提醒这一点

C. Errich-Tac-Toe

将行列按 ( i + j ) % 3 (i+j)\%3 (i+j)%3分类(染色),选择颜色中最小的特殊格子(属于某三个连续情况)个数
困难情况无非是还要按 X / O X/O X/O分成六类
easy version

#include <cstdio>
#define maxn 305
int T, n;
char s[maxn][maxn];
int cnt[5];

bool check( int i, int j ) {
	if( s[i][j] != 'X' ) return 0;
	if( j > 1 && s[i][j - 2] == 'X' && s[i][j - 1] == 'X' ) return 1;
	if( j < n && s[i][j - 1] == 'X' && s[i][j + 1] == 'X' ) return 1;
	if( j < n - 1 && s[i][j + 1] == 'X' && s[i][j + 2] == 'X' ) return 1;
	if( i > 1 && s[i - 2][j] == 'X' && s[i - 1][j] == 'X' ) return 1;
	if( i < n && s[i - 1][j] == 'X' && s[i + 1][j] == 'X' ) return 1;
	if( i < n - 1 && s[i + 1][j] == 'X' && s[i + 2][j] == 'X' ) return 1;
	return 0;
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%s", s[i] + 1 );
		cnt[0] = cnt[1] = cnt[2] = 0;
		for( int i = 1;i <= n;i ++ )
			for( int j = 1;j <= n;j ++ )
				if( check( i, j ) ) cnt[( i + j ) % 3] ++;
		int minn = 1e9, ip;
		for( int i = 0;i < 3;i ++ )
			if( cnt[i] < minn ) minn = cnt[i], ip = i;
		for( int i = 1;i <= n;i ++ ) {
			for( int j = 1;j <= n;j ++ )
				if( check( i, j ) && ( ( i + j ) % 3 == ip ) )
					printf( "O" );
				else
					printf( "%c", s[i][j] );
			printf( "\n" );
		}
	}
	return 0;
}

hard version

#include <cstdio>
#include <cstring>
#define maxn 305
int T, n;
char s[maxn][maxn];
int cnt[2][3];

void solve( int c1, int c0 ) {
	for( int i = 1;i <= n;i ++ ) {
		for( int j = 1;j <= n;j ++ )
			if( ( i + j ) % 3 == c1 && s[i][j] == 'X' )
				printf( "O" );
			else if( ( i + j ) % 3 == c0 && s[i][j] == 'O' )
				printf( "X" );
			else
				printf( "%c", s[i][j] );
		printf( "\n" );
	}
}

int main() {
	scanf( "%d", &T );
	again :
	while( T -- ) {
		scanf( "%d", &n );
		int k = 0;
		memset( cnt, 0, sizeof( cnt ) );
		for( int i = 1;i <= n;i ++ ) {
			scanf( "%s", s[i] + 1 );
			for( int j = 1;j <= n;j ++ )
				switch( s[i][j] ) {
					case 'X' : {
						cnt[1][( i + j ) % 3] ++;
						k ++;
						break;
					}
					case 'O' : {
						cnt[0][( i + j ) % 3] ++;
						k ++;
						break;
					}
				}
		}
		for( int i = 0;i < 3;i ++ )
			for( int j = 0;j < 3;j ++ )
				if( i == j ) continue;
				else if( cnt[1][i] + cnt[0][j] <= k / 3 ) {
					solve( i, j );
					goto again;
				}
	}
	return 0;
} 

D. Rating Compression

  • 如果 k = 1 k=1 k=1,要求原序列是个排列
  • 如果 k = n k=n k=n,要求序列有 1 1 1
  • 如果 1 < k < n 1<k<n 1<k<n,如果最后的结果为排列,则 1 1 1只能出现一次,发现如果 1 1 1不出现在 1 / n 1/n 1/n位置上,就必定会出现 ≥ 2 ≥2 2
    综上,得出规律,定义 l , r l,r l,r,要求对于 i i i一定出现在 l / r l/r l/r,递归下去
#include <cstdio>
#define maxn 300005
int T, n;
int a[maxn], cnt[maxn];
bool ans[maxn];
 
int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ )
			cnt[i] = ans[i] = 0;
		for( int i = 1;i <= n;i ++ )
			scanf( "%d", &a[i] ), cnt[a[i]] ++;
		for( int i = 1;i <= n;i ++ )
			if( cnt[i] == 1 ) continue;
			else goto pass;
		ans[1] = 1;
		pass :
		ans[n] = cnt[1];
		int l = 1, r = n;
		for( int i = n - 1;i;i -- ) {
			int nxt = n - i;
			cnt[nxt] --;
			if( ! cnt[nxt] && ( a[l] == nxt || a[r] == nxt ) && cnt[nxt + 1] ) {
				if( a[l] == nxt ) l ++;
				else r --;
				ans[i] = 1;
			}
			else break;
		}
		for( int i = 1;i <= n;i ++ )
			printf( "%d", ans[i] );
		printf( "\n" );
	}
	return 0;
}

E. Capitalism

∣ a u − a v ∣ = 1 → |a_u-a_v|=1\rightarrow auav=1 差分约束 → \rightarrow 图论最短路
确定方向的,相当于 1 ≤ a v − a u ≤ 1 1\le a_v-a_u\le 1 1avau1 ( u → v ) = 1 , ( v → u ) = 1 (u\rightarrow v)=1,(v\rightarrow u)=1 (uv)=1,(vu)=1
未知方向的,相当于 − 1 ≤ a v − a u ≤ 1 -1\le a_v-a_u\le 1 1avau1 ( u → v ) = ( v → u ) = 1 (u\rightarrow v)=(v\rightarrow u)=1 (uv)=(vu)=1
不能出现负环,嫉妒冲突了
而且一定是个二部图 a u ≠ a v a_u≠a_v au=av,可以用二分图染色判断

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 205
int n, m;
int dis[maxn][maxn];
bool vis[maxn];
int c[maxn];

int Fabs( int x ) {
	return x < 0 ? -x : x;
}

void dfs( int now ) {
	vis[now] = 1;
	for( int i = 1;i <= n;i ++ )
		if( i != now && dis[now][i] <= n ) {
			if( ! vis[i] ) c[i] = c[now] ^ 1, dfs( i );
			else if( c[i] == c[now] ) {
				printf( "NO\n" );
				exit( 0 );
			}
		}
}

int main() {
	scanf( "%d %d", &n, &m );
	memset( dis, 0x3f, sizeof( dis ) );
	for( int i = 1;i <= n;i ++ ) dis[i][i] = 0;
	for( int i = 1, u, v, w;i <= m;i ++ ) {
		scanf( "%d %d %d", &u, &v, &w );
		dis[u][v] = 1, dis[v][u] = w ? -1 : 1;
	}
	dfs( 1 );
	for( int k = 1;k <= n;k ++ )
		for( int i = 1;i <= n;i ++ )
			for( int j = 1;j <= n;j ++ )
				dis[i][j] = min( dis[i][j], dis[i][k] + dis[k][j] );
	for( int i = 1;i <= n;i ++ )
		if( dis[i][i] < 0 ) return ! printf( "NO\n" ); 	
	int ans = -1, pos;
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= n;j ++ )
			if( Fabs( dis[i][j] ) > ans ) ans = Fabs( dis[i][j] ), pos = i;
	printf( "YES\n%d\n", ans );
	for( int i = 1;i <= n;i ++ )	
		printf( "%d ", dis[pos][i] + n );
	return 0;
}

F. The Struggling Contestant

不相邻的最小,显然要将相邻的数绑在一起移动

k k k表示连续相同的对数, f ( i ) f(i) f(i)表示 i i i成为端点的次数,可行解的条件为 max ⁡ ( f ( i ) ) ≤ k + 2 \max\bigg(f(i)\bigg)\le k+2 max(f(i))k+2

假设有 k + 1 k+1 k+1段,那么有 2 k + 2 2k+2 2k+2个端点,除掉最左边和最右边的两个端点

还有 2 k 2k 2k个端点,每两个端点只能选一个,有 k k k个,加在一起最多也就是 k + 2 k+2 k+2

因此最大值为 k + 2 k+2 k+2

接下来证明可以构造出解来

x x x为出现次数最多的数, y ( ≠ x ) y(≠x) y(=x)为两个段的端点,进行合并,则 k , f ( x ) , f ( y ) k,f(x),f(y) k,f(x),f(y) − 1 -1 1,仍满足 f ( x ) , f ( y ) ≤ k + 2 f(x),f(y)\le k+2 f(x),f(y)k+2的关系式

对于 z ( ≠ x ≠ y ) z(≠x≠y) z(=x=y),合并前 f ( z ) ≤ 2 k + 2 − f ( x ) ≤ 2 k + 2 − f ( z ) ⇒ f ( z ) ≤ k + 1 f(z)\le 2k+2-f(x)\le 2k+2-f(z)\Rightarrow f(z)\le k+1 f(z)2k+2f(x)2k+2f(z)f(z)k+1合并后 k − 1 k-1 k1,仍满足 f ( z ) ≤ k + 2 f(z)\le k+2 f(z)k+2

这样每次将 k k k减一至零,一共 k k k次操作,整个过程都是 f ( ) ≤ k + 2 f()\le k+2 f()k+2

现在考虑 max ⁡ ( f ( x ) ) > k + 2 \max\bigg(f(x)\bigg)>k+2 max(f(x))>k+2的情况,想办法切割成 ≤ \le

如果在切割时包含了 x x x,那么 f ( x ) , k f(x),k f(x),k均增加,情况并未发生改变

所以需要在 y ( ≠ x ) , z ( ≠ x ) y(≠x),z(≠x) y(=x),z(=x)两个点之间进行切割,这样会导致 k k k增加

重复这样的操作直到变化,操作次数为 max ⁡ ( f ( x ) ) − ( k + 2 ) \max\bigg(f(x)\bigg)-(k+2) max(f(x))(k+2)

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
int T, n;
int a[maxn], cnt[maxn], f[maxn];

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d",&n );
		for( int i = 1;i <= n;i ++ )
			f[i] = cnt[i] = 0;
		int k = 0;
		for( int i = 1;i <= n;i ++ ) {
			scanf( "%d", &a[i] );
			cnt[a[i]] ++;
			if( a[i] == a[i - 1] ) {
				k ++;
				f[a[i]] += 2;
			}
		}
		f[a[1]] ++, f[a[n]] ++;
		int max_cnt = 0, max_f = 0;
		for( int i = 1;i <= n;i ++ ) {
			max_cnt = max( max_cnt, cnt[i] );
			max_f = max( max_f, f[i] );
		}
		if( ( max_cnt << 1 ) > n + 1 ) printf( "-1\n" );
		else printf( "%d\n", k + max( 0, max_f - ( k + 2 ) ) );
	}
	return 0;
}

G. Communism

去掉6个字母恰好只剩20个,刚好是可以状压的范围 用意令人深思

l i : l_i: li:字符集为 i i i的最早出现位置, r i : r_i: ri:字符集为 i i i的最晚出现位置, c n t i : cnt_i: cnti:字符集为 i i i出现次数

定义 d p s : dp_s: dps:字符集为 s s s时,能否被转移到

  • d p 0 = 1 dp_0=1 dp0=1

  • s = { i } s=\{i\} s={i}只有一个字符时,如果满足 k × ( r i − l i + 1 ) ≤ c n t i ⇒ d p s = 1 k\times(r_i-l_i+1)\le cnt_i\Rightarrow dp_s=1 k×(rili+1)cntidps=1

  • 对于某一个字符 i i i,一开始无法操作,如果可以先操作 s − i s-i si剩下字符再操作 i i i就可以

    [ i ∈ s ] ⋂ [ d p s − x = 1 ] ⋂ [ k × ( r s − l s + 1 ) ≤ c n t s ] ⇒ d p s = 1 [i∈s]\bigcap[dp_{s-x}=1]\bigcap[k\times(r_s-l_s+1)\le cnt_s]\Rightarrow dp_s=1 [is][dpsx=1][k×(rsls+1)cnts]dps=1

  • 当字符集为两不相交的字符集的并,且两字符集都可以操作,那么合并后的字符集也同样可以操作

    [ s = i ⋃ j ] ⋂ [ i ⋂ j = ∅ ] ⋂ [ d p i = d p j = 1 ] ⇒ d p s = 1 [s=i\bigcup j]\bigcap[i\bigcap j=\empty]\bigcap[dp_i=dp_j=1]\Rightarrow dp_s=1 [s=ij][ij=][dpi=dpj=1]dps=1

但是因为性质四,涉及子集枚举, O ( 3 20 ) O(3^{20}) O(320),考虑优化

性质:当且仅当两个字符集的管辖区间 l i , r i l_i,r_i li,ri不相交,才需要考虑这样的转移

因为如果区间有相交,那么一定是性质三的转移不差于性质四转移

两段相交区间,唯一变的就是 r − l + 1 r-l+1 rl+1,变小,则可以适应更大的 k k k,更容易转移,所以选择一个吃掉另一个再转移

那么这个优化就可以按照 l l l排序,前缀和优化

最后如果 d p U ⨁ i = 1 dp_{U\bigoplus i}=1 dpUi=1,则该字符是可以被操作成整字符串的

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 5005
#define alpha 150
#define maxs 1 << 20
int n, a, b;
bool dp[maxs];
char S[maxn], MS[alpha];
int id[alpha], l[maxs], r[maxs], cnt[maxs], s[maxn];

bool cmp( int x, int y ) {
	return l[1 << x] < l[1 << y];
}

int lowbit( int x ) {
	return x & ( -x );
}

int main() {
	int n, a, b;
	scanf( "%d %d %d %s", &n, &a, &b, S + 1 );
	int ip = -1, U = 0;
	for( char i = 'a';i <= 'z';i ++ )
		if( i == 't' || i == 'r' || i == 'y' || i == 'g' || i == 'u' || i == 'b' )
			continue;
		else
			id[i] = ++ ip, MS[ip] = i;
	for( int i = 1;i <= n;i ++ )
		s[i] = id[S[i]], U |= ( 1 << s[i] );
	for( int i = 0;i < 20;i ++ )
		id[i] = i, l[1 << i] = n;
	for( int i = 1;i <= n;i ++ ) {
		l[1 << s[i]] = min( i, l[1 << s[i]] );
		r[1 << s[i]] = i;
		cnt[1 << s[i]] ++;
	}
	sort( id, id + 20, cmp );
	for( int i = 0;i < maxs;i ++ )
		if( i ^ lowbit( i ) ) {
			l[i] = min( l[i ^ lowbit( i )], l[lowbit( i )] );
			r[i] = max( r[i ^ lowbit( i )], r[lowbit( i )] );
			cnt[i] = cnt[i ^ lowbit( i )] + cnt[lowbit( i )];
		}
	dp[0] = 1;
	for( int i = 1;i < maxs;i ++ ) {
		if( ( i & U ) != i ) continue;
		if( ( r[i] - l[i] + 1 ) * a <= cnt[i] * b ) {
			if( i == lowbit( i ) ) dp[i] = 1;
			else {
				for( int j = 0;j < 20;j ++ )
					if( 1 << j & i ) dp[i] |= dp[1 << j ^ i];
			}
		}
		if( ! dp[i] ) {
			int k = 0;
			for( int j = 0;j < 20;j ++ ) {
				k = ( 1 << id[j] | k ) & i;
				dp[i] |= dp[i ^ k] & dp[k];
			}
		}
	}
	int ans = 0;
	for( int i = 0;i < 20;i ++ )
		if( dp[1 << i ^ U] ) ans ++;
	printf( "%d", ans );
	for( int i = 0;i < 20;i ++ )
		if( dp[1 << i ^ U] )
			printf( " %c", MS[i] );
	return 0;
}

Educational-Round-111

C. Manhattan Subarrays

曼哈顿距离,发现若 [ l , r ] [l,r] [l,r]内存在三个 递增 / 递减 / 两平加一 的点,则一定不是好区间

因此发现,好区间的长度最多不超过4,直接暴力可做

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int T, n, ans;
int a[maxn];

int Fabs( int x ) {
	return x < 0 ? -x : x;
}

int dis( int i, int j ) {
	return Fabs( i - j ) + Fabs( a[i] - a[j] );
}

void calc( int l, int r ) {
	for( int i = l;i <= r;i ++ )
		for( int j = i + 1;j <= r;j ++ )
			for( int k = j + 1;k <= r;k ++ )
				if( dis( i, j ) + dis( j, k ) == dis( i, k ) ) return;
	ans ++;
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%d", &a[i] );
		ans = 0;
		for( int i = 1;i <= n;i ++ )
			for( int j = i;j <= min( n, i + 3 );j ++ )
				calc( i, j );
		printf( "%d\n", ans );
	}
	return 0;
} 

D. Excellent Arrays

  • a i + a j = i + j ⇔ a i − i = − ( a j − j ) a_i+a_j=i+j\Leftrightarrow a_i-i=-(a_j-j) ai+aj=i+jaii=(ajj)

    定义 w i = a i − i ⇒ a i − i = w i → w i = − w j w_i=a_i-i\Rightarrow a_i-i=w_i\rightarrow w_i=-w_j wi=aiiaii=wiwi=wj

  • { a i + a j = i + j a j + a k = j + k a i + a k = i + k ⇒ { w i = − w j w j = − w k w i = − w k \begin{cases} a_i+a_j=i+j\\ a_j+a_k=j+k\\ a_i+a_k=i+k\\ \end{cases} \Rightarrow \begin{cases} w_i=-w_j w_j=-w_k w_i=-w_k \end{cases} ai+aj=i+jaj+ak=j+kai+ak=i+k{wi=wjwj=wkwi=wk

    若想要三个中任意两个都能配对,当且仅当 a i = i a_i=i ai=i,但不满足好序列要求

    因此所有数只能分成两部分,不同部分间彼此配对

  • 配对的数量最大化,显然是一半的数为 w w w,另外一半的数为 − w -w w

    这样配对数量最多,可以达到 ( n 2 ) 2 (\frac{n}{2})^2 (2n)2级(正方形面积问题)

    F ( a ) = ⌊ n 2 ⌋ ⋅ ⌈ n 2 ⌉ F(a)=\lfloor\frac{n}{2}\rfloor·\lceil{\frac{n}{2}\rceil} F(a)=2n2n

  • 但是有的 i i i,可能只能是 w w w类型,有的可能只能是 − w -w w类型, 有的二者皆可

  • 定义 h a l f = ⌊ n 2 ⌋ half=\lfloor\frac{n}{2}\rfloor half=2n,不同范围 w w w的计数(假设 w > 0 w>0 w>0

    有两个不等式
    { l ≤ a i = w i + i ≤ r l ≤ a i = − w i + i ≤ r ⇒ { l − i ≤ w i ≤ r − i i − r ≤ w i ≤ i − l \begin{cases} l\le a_i=w_i+i\le r\\ l\le a_i=-w_i+i\le r \end{cases} \Rightarrow \begin{cases} l-i\le w_i\le r-i\\ i-r\le w_i\le i-l \end{cases} {lai=wi+irlai=wi+ir{liwiriirwiil

    • 如果 max ⁡ ( l − i , i − r ) ≤ w i ≤ min ⁡ ( r − i , i − l ) ⇒ w ≤ min ⁡ ( r − n , 1 − l ) \max(l-i,i-r)\le w_i\le \min(r-i,i-l)\Rightarrow w\le \min(r-n,1-l) max(li,ir)wimin(ri,il)wmin(rn,1l),则整个 [ l , r ] [l,r] [l,r]都是二者类型

      • n = 2 k → C n h a l f n=2k\rightarrow C_{n}^{half} n=2kCnhalf
      • n = 2 k + 1 → C n h a l f + C n h a l f + 1 n=2k+1\rightarrow C_{n}^{half}+C_{n}^{half+1} n=2k+1Cnhalf+Cnhalf+1
    • 如果 w > min ⁡ ( r − n , 1 − l ) w>\min(r-n,1-l) w>min(rn,1l),则 ∀ i ∈ [ 1 , L ) a i \forall_{i∈[1,L)}a_i i[1,L)ai只能是 w w w类型, ∀ i ∈ ( R , n ] a i \forall_{i∈(R,n]}a_i i(R,n]ai只能是 − w -w w类型

      { L = min ⁡ ( 1 , l + w ) ; R = max ⁡ ( n , r − w ) } \bigg\{L=\min(1,l+w);R=\max(n,r-w)\bigg\} {L=min(1,l+w);R=max(n,rw)}

      R − L + 1 R-L+1 RL+1个则是自由二者皆可

      枚举 w w w,当 L , R L,R L,R冲突时break

      • n = 2 k → C R − L + 1 h a l f − ( L − 1 ) n=2k\rightarrow C_{R-L+1}^{half-(L-1)} n=2kCRL+1half(L1)
      • n = 2 k + 1 → C R − L + 1 h a l f − ( L − 1 ) + C R − L + 1 h a l f − ( L − 1 ) + 1 n=2k+1\rightarrow C_{R-L+1}^{half-(L-1)}+C_{R-L+1}^{half-(L-1)+1} n=2k+1CRL+1half(L1)+CRL+1half(L1)+1

时间复杂度 O ( n log ⁡ M ) O(n\log M) O(nlogM)

#include <cstdio>
#include <iostream>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 200000
int fac[maxn << 1], inv[maxn << 1];
int T, n, l, r;

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 || m < 0 ) 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 %lld", &n, &l, &r );
		int k = min( 1 - l, r - n ), half = n >> 1, ans = 0;
		if( n & 1 ) 
			ans = ( k * C( n, half ) % mod + k * C( n, half + 1 ) % mod ) % mod;
		else
			ans = k * C( n, half );
		while( ++ k ) {
			int L = max( 1ll, l + k ), R = min( r - k, n ), len = R - L + 1;
			if( len < 0 ) break;
			else
				if( n & 1 )
					ans = ( ans + C( len, half - ( L - 1 ) ) + C( len, half - ( L - 1 ) + 1 ) ) % mod;
				else
					ans = ( ans + C( len, half - ( L - 1 ) ) ) % mod;
		}
		printf( "%lld\n", ans );
	}
	return 0;
}

E. Stringforces

  • 二分答案长度len

  • check长度,想要在 n n n以内使得前 k k k个罗马小写字符都连续出现至少len

    转化为让前 k k k个小写字符都连续出现至少一次的至少len个的最短长度

  • d p s : dp_s: dps: 已经构造的字符集为s,字符集内每个字符都满足要求的最短长度

  • p o s i , j : pos_{i,j}: posi,j:i往后的某个位置开始填连续lenj字符的结束位置

    需要保证,整段除了?就是j,定义 l a s t i : last_i: lasti: i字符上次出现位置,枚举字符判断是否在段内即可

  • 转移: d p s ∣ 1 < < j = min ⁡ { p o s d p s + 1 , j } dp_{s|1<<j}=\min\{pos_{dp_s+1,j}\} dps1<<j=min{posdps+1,j}

  • 最后保证长度 ≤ n \le n n即可

#include <cstdio>
#include <cstring> 
#include <iostream>
using namespace std;
#define inf 0x7f7f7f7f
#define maxn 200005
#define alpha 17
int n, k, S;
char s[maxn];
int dp[1 << alpha], last[maxn];
int pos[maxn][alpha];
bool check( int len ) {
	memset( pos, 0x7f, sizeof( pos ) );
	memset( last, 0x7f, sizeof( last ) );
	for( int i = n;i;i -- ) {
		if( s[i] != '?' ) last[s[i] - 'a'] = i; else;
		for( int j = 0;j < k;j ++ ) {
			pos[i][j] = ( i + len - 1 <= n ) ? i + len - 1 : pos[i + 1][j];
			for( int w = 0;w < k;w ++ )
				if( j == w ) continue;
				else 
					if( last[w] <= i + len - 1 ) {
						pos[i][j] = pos[i + 1][j];
						break;
					} else;
		}
	}
	memset( dp, 0x7f, sizeof( dp ) );
	dp[0] = 0;
	for( int i = 0;i < S;i ++ )
		for( int j = 0;j < k;j ++ )
			if( ( 1 << j & i ) || dp[i] == inf ) continue;
			else dp[1 << j | i] = min( dp[1 << j | i], pos[dp[i] + 1][j] );
	return dp[S - 1] <= n;
}

int main() {
	scanf( "%d %d %s", &n, &k, s + 1 );
	S = 1 << k;
	int l = 0, r = n / k, ans = 0;
	while( l <= r ) {
		int mid = ( l + r ) >> 1;
		if( check( mid ) ) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	printf( "%d\n", ans );
	return 0;
}

F. Jumping Around

boruvka最小生成树
既有kruskal的并查集合并,又有prim的扩展,因而正确性得到保证

  • 初始化,每个点为一个联通块
  • 根据题目选择数据结构,快速找到两个联通块之间的最大/最小边权
  • 并查集按秩合并,建图
  • 利用图的dfs树上的lca等信息求解

对于此题,两点互达的最小需要 k k k即为边权
具体代码:
枚举联通块,set维护点,对于联通块内的每个点
先把同联通块的点扔出去
set查找距离点最近的前后点(隶属不同联通块)
边权 ∣ ∣ a i − a x ∣ − d ∣ \big||a_i-a_x|-d\big| aiaxd
建图,从 s s s开始走dfs树,记录到每个点途经的最大值
那就是最小生成树的瓶颈边,判断给定的 k k k与该边的关系即可

#include <set>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define inf 0x7f7f7f7f
#define maxn 1000005
struct node {
	int u, v, w;
	node(){}
	node( int U, int V, int W ) {
		u = U, v = V, w = W;
	}
};
set < int > pos;
set < int > :: iterator it;
vector < int > MS[maxn];
vector < pair < int, int > > G[maxn];
int n, Q, s, d;
int ans[maxn], x[maxn], f[maxn], id[maxn];

int Fabs( int x ) {
	return x < 0 ? -x : x;
}

bool operator < ( node x, node y ) {
	return x.w < y.w;
}

void dfs( int u, int fa, int val ) {
	ans[u] = val;
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i].first;
		if( v == fa ) continue;
		else dfs( v, u, max( val, G[u][i].second ) );
	}
}

bool merge( int u, int v ) {
	u = f[u], v = f[v]; 
	if( u == v ) return 0;
	else {
		if( MS[u].size() < MS[v].size() ) swap( u, v );
		for( int i = 0;i < MS[v].size();i ++ )
			f[MS[v][i]] = u, MS[u].push_back( MS[v][i] );
		MS[v].clear();
		return 1;
	}
}

int main() {
	scanf( "%d %d %d %d", &n, &Q, &s, &d );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &x[i] );
		MS[i].push_back( i );
		f[i] = id[x[i]] = i;
		pos.insert( x[i] );
	}
	int cnt = n;
	while( cnt > 1 ) {
		vector < node > edge;
		for( int i = 1;i <= n;i ++ ) {
			if( ! MS[i].size() ) continue;
			else {
				for( int j : MS[i] ) pos.erase( x[j] );
				node minn = node( 0, 0, inf );
				for( int j : MS[i] )
					for( int k : { -d, d } ) {
						it = pos.lower_bound( x[j] + k );
						if( it != pos.end() )
							minn = min( minn, node( j, id[*it], Fabs( Fabs( x[j] - ( *it ) ) - d ) ) );
						if( it != pos.begin() )
							it --, minn = min( minn, node( j, id[*it], Fabs( Fabs( x[j] - ( *it ) ) - d ) ) );
					}
				for( int j : MS[i] ) pos.insert( x[j] );
				if( minn.w == inf ) continue;
				else edge.push_back( minn );
			}
		}
		for( int i = 0;i < edge.size();i ++ ) 
			if( merge( edge[i].u, edge[i].v ) ) {
				-- cnt;
				G[edge[i].u].push_back( make_pair( edge[i].v, edge[i].w ) );
				G[edge[i].v].push_back( make_pair( edge[i].u, edge[i].w ) );
			}
	}
	dfs( s, 0, 0 );
	while( Q -- ) {
		int i, k;
		scanf( "%d %d", &i, &k );
		if( ans[i] <= k ) printf( "Yes\n" );
		else printf( "No\n" );
	}
	return 0;
}

Educational-Round-106

C. Minimum Grid Path

先往上走和先往右走是等价的,因为两个方向上路径综合是相同的 n n n(假设先往右走)

直接枚举有多少段,按奇偶分开算,那么有 ⌈ i 2 ⌉ \lceil\frac{i}{2}\rceil 2i段都是向右走, ⌊ n 2 ⌋ \lfloor\frac{n}{2}\rfloor 2n都是向上走

贪心的有,花费最小的尽可能多走长度,其余的花费就只让走一步就拐即可

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 100005
int T, n;
int c[maxn], ans[maxn], sum[maxn];

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%lld", &c[i] );
		sum[1] = c[1], sum[2] = c[2];
		for( int i = 3;i <= n;i ++ )
			sum[i] = sum[i - 2] + c[i];
		int minn = c[1]; ans[1] = c[1] * n;
		for( int i = 3;i <= n;i += 2 )
			if( c[i] > minn )
				ans[i] = ans[i - 2] - minn + c[i];
			else {
				ans[i] = sum[i - 2] + c[i] * ( n - i / 2 );
				minn = c[i];
			}
		minn = c[2]; ans[2] = c[2] * n;
		for( int i = 4;i <= n;i += 2 )
			if( c[i] > minn )
				ans[i] = ans[i - 2] - minn + c[i];
			else {
				ans[i] = sum[i - 2] + c[i] * ( n - i / 2 + 1 );
				minn = c[i];
			}
		int ret = 1e18;
		for( int i = 2;i <= n;i ++ )
			ret = min( ret, ans[i] + ans[i - 1] );
		printf( "%lld\n", ret );
	}
	return 0;
}

D. The Number of Pairs

  • a = A g , b = B g ⇒ ( A , B ) = 1 a=Ag,b=Bg\Rightarrow (A,B)=1 a=Ag,b=Bg(A,B)=1,改写式子 → c ∗ A ∗ B ∗ g = x + d ∗ g \rightarrow c*A*B*g=x+d*g cABg=x+dg
    • 若要等式存在,第一个要满足的条件就是 g ∣ x g\bigg|x gx
      • O ( x ) O(\sqrt{x}) O(x )预处理出 x x x的所有因数
    • 枚举因数 g g g
      • 反解出 A B = x g + d c AB=\frac{\frac{x}{g}+d}{c} AB=cgx+d
        • 同样因为 A , B A,B A,B为整数,第二个要满足的条件就是 c ∣ ( x g + d ) c\bigg|(\frac{x}g{+d)} c(gx+d)
        • 知道 A ∗ B A*B AB后,答案就是 A ∗ B A*B AB 2 质 因 子 个 数 2^{质因子个数} 2,可以通过欧筛提前预处理
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 20000000
int cnt[maxn + 5];
bool vis[maxn + 5];

void sieve() {
	for( int i = 2;i <= maxn;i ++ )
		if( ! vis[i] )
			for( int j = i;j <= maxn;j += i )
				vis[j] = 1, cnt[j] ++;
}

int main() {
	sieve();
	int T, c, d, x;
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d %d %d", &c, &d, &x );
		vector < int > G;
		for( int i = 1;i * i <= x;i ++ )
			if( x % i == 0 ) {
				G.push_back( i );
				if( i * i != x ) G.push_back( x / i );
			}
		long long ans = 0;
		for( int i = 0;i < G.size();i ++ ) {
			int g = G[i];
			if( ( x / g + d ) % c ) continue;
			else ans += ( 1ll << cnt[( ( x / g ) + d ) / c] );
		} 
		printf( "%lld\n", ans );
	}
	return 0;
}

E. Chaotic Merge

d p i , j , k , 0 / 1 : dp_{i,j,k,0/1}: dpi,j,k,0/1: 字符串 s s s的指针指向 i i i,字符串 t t t的指针指向 j j j s i , t j s_i,t_j si,tj还未被操作),是否 s , t s,t s,t中的字符都出现过的情况 k k k(有 4 4 4种),合并串当前位是 0 − s i − 1 / 1 − t j − 1 0-s_{i-1}/1-t_{j-1} 0si1/1tj1的方案数

对应的有四种转移情况,看代码就行

但是题目是要求 [ l , r ] [l,r] [l,r]区间的,其实从 d p i + 1 , j , 1 , 0 = d p i , j + 1 , 2 , 1 = 1 dp_{i+1,j,1,0}=dp_{i,j+1,2,1}=1 dpi+1,j,1,0=dpi,j+1,2,1=1的初始化就代表了从 i , j i,j i,j开始的字符串转移

#include <cstdio>
#include <cstring>
#define maxn 1005
#define int long long
#define mod 998244353
char s[maxn], t[maxn];
int dp[maxn][maxn][4][2];

signed main() {
	scanf( "%s %s", s, t );
	int n = strlen( s ), m = strlen( t ), ans = 0;
	for( int i = 0;i <= n;i ++ )
		for( int j = 0;j <= m;j ++ ) {
			if( i < n ) dp[i + 1][j][1][0] = 1;
			if( j < m ) dp[i][j + 1][2][1] = 1;
			for( int k = 0;k < 4;k ++ ) {
				if( 0 < i && i < n && s[i - 1] != s[i] )
					dp[i + 1][j][k | 1][0] = ( dp[i + 1][j][k | 1][0] + dp[i][j][k][0] ) % mod;
				if( 0 < j && i < n && t[j - 1] != s[i] )
					dp[i + 1][j][k | 1][0] = ( dp[i + 1][j][k | 1][0] + dp[i][j][k][1] ) % mod;
				if( 0 < i && j < m && s[i - 1] != t[j] )
					dp[i][j + 1][k | 2][1] = ( dp[i][j + 1][k | 2][1] + dp[i][j][k][0] ) % mod;
				if( 0 < j && j < m && t[j - 1] != t[j] )
					dp[i][j + 1][k | 2][1] = ( dp[i][j + 1][k | 2][1] + dp[i][j][k][1] ) % mod;
			}
			ans = ( ans + dp[i][j][3][0] + dp[i][j][3][1] ) % mod;
		}
	printf( "%lld\n", ans );
	return 0;
}

F. Diameter Cuts

d p u , i : u dp_{u,i}:u dpu,i:u子树内经过 u u u的最长路径为 i i i,枚举每一个儿子 v v v转移

  • 断掉与儿子的边,枚举儿子的最长路径 j j j

    d p u , i = ∑ j ≤ i d p u , i ⋅ d p v , j dp_{u,i}=\sum_{j\le i}dp_{u,i}·dp_{v,j} dpu,i=jidpu,idpv,j

  • 不断边,那么就是 u u u某两个儿子最长路径拼起来

    d p u , i + j + 1 = ∑ j ( i + j + 1 ≤ k ) d p u , i ⋅ d p v , j dp_{u,i+j+1}=\sum_{j(i+j+1\le k)}dp_{u,i}·dp_{v,j} dpu,i+j+1=j(i+j+1k)dpu,idpv,j

每次转移枚举到儿子最长路径即可,时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 5005
vector < int > G[maxn];
int n, k;
int dp[maxn][maxn];

int dfs( int u, int fa ) {
	dp[u][0] = 1;
	int dep = 0, dep_son;
	for( int v : G[u] ) {
		if( v == fa ) continue;
		else dep_son = dfs( v, u );
		vector < int > MS( max( dep, dep_son + 1 ) + 1 );
		for( int i = 0;i <= dep;i ++ )
			for( int j = 0;j <= dep_son;j ++ ) {
				if( i + j + 1 <= k )	
					MS[max( i, j + 1 )] = ( MS[max( i, j + 1 )] + dp[u][i] * dp[v][j] ) % mod;
				if( i <= k && j <= k )
					MS[i] = ( MS[i] + dp[u][i] * dp[v][j] ) % mod;
			}
		dep = max( dep, dep_son + 1 );
		for( int i = 0;i <= dep;i ++ )
			dp[u][i] = MS[i];
	}
	return dep;
}

signed main() {
	scanf( "%lld %lld", &n, &k );
	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 );
	int ans = 0;
	for( int i = 0;i <= k;i ++ )
		ans = ( ans + dp[1][i] ) % mod;
	printf( "%lld\n", ans );
	return 0;
}

G. Graph Coloring

图一定是若干个偶环和若干条路径构成在一起

最小解的构造相当于对于一条路径/环的染色就是欧拉路径/回路的黑白染色法

环肯定可以黑白染色,删去,剩下了一张森林网络

对于一条路径而言,黑白交替染色后就是两个端点产生了贡献(度数为 1 1 1的点)

因为强制在线那么就只能真的加边,考虑在若干条路径间的加边情况

  • 两个点都不是端点

    那么直接两个点产生了一条新的路径

  • 只有一个点是端点

    直接把另一个点接上端点成为路径的新端点

  • 两个点都是端点

    但此时要注意两个端点的边染色是否相同,必须相同两个端点间的连边才能染成相反的颜色

    如果不同,直接翻转某一个端点的路径即可,个数是不会发生变化的

s u m x , 0 / i sum_{x,0/i} sumx,0/i表示 0 − b l u e / 1 − r e d 0-blue/1-red 0blue/1red x x x路径 h a s h hash hash答案

上述染色问题的过程,带权并查集简直是专攻

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 400005
vector < int > cnt;
int n, m, Q, ans;
int f[maxn], c[maxn], to[maxn], bit[maxn];
int sum[maxn][2];

int find( int x ) {
	if( f[x] == x ) return x;
	if( f[f[x]] == f[x] ) return f[x];
	int fa = find( f[x] );
	c[x] ^= c[f[x]];
	return f[x] = fa;
}

int color( int x ) {
	if( x == f[x] ) return c[x];
	int fa = find( x );
	return c[x] ^ c[fa];
}

void reverse( int x ) {
	x = find( x );
	ans = ( ans - sum[x][c[x]] + mod ) % mod;
	c[x] ^= 1;
	ans = ( ans + sum[x][c[x]] ) % mod;
}

void merge( int x, int y ) {
	x = find( x ), y = find( y );
	if( x == y ) return;
	ans = ( ans - sum[x][c[x]] + mod ) % mod;
	ans = ( ans - sum[y][c[y]] + mod ) % mod;
	c[x] ^= c[y], f[x] = y;
	sum[y][0] = ( sum[y][0] + sum[x][c[x]] ) % mod;
	sum[y][1] = ( sum[y][1] + sum[x][! c[x]] ) % mod;
	ans = ( ans + sum[y][c[y]] ) % mod;
}

void link( int x, int y, int id ) {
	sum[id][1] = bit[id] = ( bit[id - 1] << 1 ) % mod, f[id] = id;//起初默认id边颜色为blue(0)
	if( ! to[x] && ! to[y] ) {//两个都不是端点 新增一条路径
		reverse( id );
		to[x] = to[y] = id;
	}
	else if( ! to[x] || ! to[y] ) {//只有一个端点直接连起来
		if( ! to[x] ) swap( x, y );
		if( ! color( to[x] ) ) reverse( id );//端点边颜色为blue那么新连边就为red(1)
		merge( to[x], id );
		to[x] = 0, to[y] = id;
	}
	else {//两个都是端点
		if( color( to[x] ) != color( to[y] ) ) reverse( to[x] );
//必须两端点边颜色相同 其之间的两边才能是另一种颜色 否则需要先翻转其中一条路径
		if( ! color( to[x] ) ) reverse( id );
		merge( to[x], id ), merge( to[y], id );
		to[x] = to[y] = 0;
	}
}

signed main() {
	int n1, n2; bit[0] = 1;
	scanf( "%lld %lld %lld", &n1, &n2, &m );
	for( int i = 1, u, v;i <= m;i ++ ) {
		scanf( "%lld %lld", &u, &v );
		link( u, v + n1, i );
	}
	scanf( "%lld", &Q );
	while( Q -- ) {
		int opt, u, v;
		scanf( "%lld", &opt );
		if( opt & 1 ) {
			scanf( "%lld %lld", &u, &v );
			link( u, v + n1, ++ m );
			printf( "%lld\n", ans );
		}
		else {
			cnt.clear();
			for( int i = 1;i <= m;i ++ )
				if( color( i ) ) cnt.push_back( i );
			printf( "%lld ", cnt.size() );
			for( int i = 0;i < cnt.size();i ++ )
				printf( "%lld ", cnt[i] );
			printf( "\n" );
		}
		fflush( stdout );
	}
	return 0;
}

AtCoder

ARC-122

A - Many Formulae

考虑从前/从后递推到 i i i时, i i i前面符号为+/-的情况数,乘上相应的代价即可

#include <cstdio>
#define int long long
#define mod 1000000007
#define maxn 100005
int n;
int a[maxn];
int f[maxn][2], g[maxn][2];

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &a[i] );
	f[1][1] = 1;
	for( int i = 2;i <= n;i ++ ) {
		f[i][0] = f[i - 1][1];
		f[i][1] = ( f[i - 1][0] + f[i - 1][1] ) % mod;
	}
	g[n][0] = g[n][1] = 1;
	for( int i = n - 1;i;i -- ) {
		g[i][0] = g[i + 1][1];
		g[i][1] = ( g[i + 1][0] + g[i + 1][1] ) % mod;
	}
	g[1][0] = 0;
	int ans = 0;
	for( int i = 1;i <= n;i ++ )
		ans = ( ans - a[i] * f[i][0] % mod * g[i][0] % mod + a[i] * f[i][1] % mod * g[i][1] % mod + mod ) % mod;
	printf( "%lld\n", ans );
	return 0;
}

B - Insurance

扑面而来的三分气息

看题目描述不可能是二分,看看式子,更加觉得三分有理

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
#define eps 1e-7
int n;
double A[maxn];

double check( double x ) {
	double ans = 0;
	for( int i = 1;i <= n;i ++ )
		ans += x + A[i] - min( A[i], x * 2 );
	return ans / n;
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lf", &A[i] );
	double l = 0, r = 1e9;
	while( r - l > eps ) {
		double mid1 = l + ( r - l ) / 3, mid2 = r - ( r - l ) / 3;
		if( check( mid1 ) > check( mid2 ) ) l = mid1;
		else r = mid2;
	}
	printf( "%.10f\n", check( l ) );
	return 0;
}

C - Calculator

果然是套斐波拉契,看操作就能发现端倪,更全面的应该是斐波拉契套倍增

#include <stack>
#include <cstdio>
#include <vector>
using namespace std;
#define int long long
stack < int > st;
vector < int > ans;
int fib[100];
int n;

signed main() {
	scanf( "%lld", &n );
	fib[1] = fib[2] = 1;
	for( int i = 3;i <= 88;i ++ )
		fib[i] = fib[i - 1] + fib[i - 2];
	for( int i = 88;i;i -- )
		if( n >= fib[i] ) st.push( i ), n -= fib[i];
	int ip = 0;
	while( ! st.empty() ) {
		int x = st.top(); st.pop();
		if( x & 1 ) {
			while( ip < x - 1 ) {
				if( ip & 1 ) ans.push_back( 4 );
				else ans.push_back( 3 );
				ip ++;
			}
			ans.push_back( 1 );
		}
		else {
			while( ip < x - 1 ) {
				if( ip & 1 ) ans.push_back( 4 );
				else ans.push_back( 3 );
				ip ++;
			}
			ans.push_back( 2 );
		}
	}
	printf( "%lld\n", ans.size() );
	for( int i = ans.size() - 1;~ i;i -- )
		printf( "%lld\n", ans[i] );
}

D - XOR Game

显然是跟二进制挂钩

想最后异或的答案最小,换言之贪心二进制从高位到低位顺次考虑尽可能使高位为 0 0 0

那么就需要 0 − 0 , 1 − 1 0-0,1-1 00,11,除非迫不得已再 0 − 1 0-1 01从而导致该位产生贡献

而这迫不得已的情况就是该位为 0 0 0( 1 1 1)的数字个数为奇数时,就必定会产生一条 0 − 1 0-1 01的配对

否则按 0 − 0 , 1 − 1 0-0,1-1 00,11配对方式将数划分成两堆各自重复上述过程

显然这个应该放在字典树上去搞,套着递归求解

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int cnt = 1, n, ret;
int siz[maxn * 30], trie[maxn * 30][2];

void insert( int x ) {
	int now = 1;
	siz[now] ++;
	for( int i = 29;~ i;i -- ) {
		int k = x >> i & 1;
		if( ! trie[now][k] ) trie[now][k] = ++ cnt;
		now = trie[now][k];
		siz[now] ++;
	}
}

int work( int x1, int x2, int d ) {
	int ans = 1 << 30;
	if( ! x1 ) return ans;
	if( ! d ) return 0;
	for( int i = 0;i < 2;i ++ )
		if( trie[x2][i] ) 
			ans = min( ans, work( trie[x1][i], trie[x2][i], d - 1 ) );
		else
			ans = min( ans, work( trie[x1][i], trie[x2][i ^ 1], d - 1 ) + ( 1 << d - 1 ) );
	return ans;
}

void solve( int x, int d ) {
	if( ! x ) return;
	if( siz[trie[x][0]] & 1 )
		ret = max( ret, work( trie[x][0], trie[x][1], d - 1 ) + ( 1 << d - 1 ) );
	else {
		solve( trie[x][0], d - 1 );
		solve( trie[x][1], d - 1 );
	}
}

int main() {
	scanf( "%d", &n );
	for( int i = 1, x;i <= ( n << 1 );i ++ ) {
		scanf( "%d", &x );
		insert( x );
	}
	solve( 1, 30 );
	printf( "%d\n", ret );
	return 0;
}

E - Increasing LCMs

要想 l c m lcm lcm单增,那么每次的最后数字 A i A_i Ai一定是非常特殊的,它满足拥有一个 [ 1 , i ) [1,i) [1,i)都没有的质因子的幂

贪心的有每次选择这样特殊的数字扔到最后,显然扔的数字越多,剩下的数成为特殊数字的可能性越大

直接暴力模拟选取

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long 
#define maxn 105
int n;
int A[maxn], id[maxn];
int d[maxn][maxn];

int gcd( int x, int y ) {
	if( x < y ) swap( x, y );
	if( ! y ) return x;
	else return gcd( y, x % y );
}

int lcm( int x, int y ) {
	return x / gcd( x, y ) * y;
}

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &A[i] ), id[i] = i;
	for( int i = 1;i <= n;i ++ )
		for( int j = i + 1;j <= n;j ++ )
			d[i][j] = d[j][i] = gcd( A[i], A[j] );
	for( int i = n;i;i -- ) {
		bool flag = 0;
		for( int j = 1;j <= i;j ++ ) {
			int g = 1;
			for( int k = 1;k <= i;k ++ )
				if( j == k ) continue;
				else g = lcm( g, d[id[j]][id[k]] );
			if( g == A[id[j]] ) continue;
			else {
				for( int k = j;k < i;k ++ )
					swap( id[k], id[k + 1] );
				flag = 1;
				break;
			}
		}
		if( ! flag ) return ! printf( "No\n" );
	}
	printf( "Yes\n" );
	for( int i = 1;i <= n;i ++ )
		printf( "%lld ", A[id[i]] );
	return 0;
} 

ABC-206

A - Maxi-Buying

翻译题面,条件判断即可

B - Savings

x ( x − 1 ) 2 ≥ n , O ( n ) \frac{x(x-1)}{2}\ge n,O(\sqrt{n}) 2x(x1)n,O(n )枚举 x x x即可

C - Swappable

O ( n ) O(n) O(n)线性,边输入边做, m a p map map统计 x x x出现次数,用已统计的个数减去出现次数即可

D - KAIBUNsyo

直接扫,不对应随便修改其中一个即可,因为修改需要传递,直接并查集维护即可

E - Divide Both

做过一道非常相似的莫比乌斯反演,求 g c d ( x , y ) = 1 , a ≤ x ≤ b , c ≤ y ≤ d gcd(x,y)=1,a\le x\le b,c\le y\le d gcd(x,y)=1,axb,cyd的数对个数

拆分成 S ( b , d ) − S ( a , d ) − S ( b , c ) + S ( a , c ) S(b,d)-S(a,d)-S(b,c)+S(a,c) S(b,d)S(a,d)S(b,c)+S(a,c)

其中 S ( n , m ) : g c d ( x , y ) = 1 , 1 ≤ x ≤ n , 1 ≤ y ≤ m S(n,m):gcd(x,y)=1,1\le x\le n,1\le y\le m S(n,m):gcd(x,y)=1,1xn,1ym的数对个数

对于此题,先发制人枚举 g c d gcd gcd,转化为求 g c d ( x , y ) = 1 , ⌈ L g ⌉ ≤ x , y ≤ ⌊ R g ⌋ gcd(x,y)=1,\lceil\frac{L}{g}\rceil\le x,y\le \lfloor\frac{R}{g}\rfloor gcd(x,y)=1,gLx,ygR的数对个数

在最后枚举 i ∈ [ L , R ] i∈[L,R] i[L,R]减去其因子个数 c n t cnt_{} cnt和其倍数 R i \frac{R}{i} iR

#pragma GCC optimize(2)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define maxn 1000005
vector < int > d[maxn];
int cnt;
bool vis[maxn];
int prime[maxn], mu[maxn], pre[maxn];

void init( int n ) {
	mu[1] = 1;	
	for( int i = 2;i <= n;i ++ ) {
		if( ! vis[i] ) prime[++ cnt] = i, mu[i] = -1;
		for( int j = 1;j <= cnt && i * prime[j] <= n;j ++ ) {
			vis[i * prime[j]] = 1;
			if( i % prime[j] == 0 ) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
	for( int i = 1;i <= n;i ++ ) pre[i] = pre[i - 1] + mu[i];
}

int calc( int n, int m ) {
	if( n > m ) swap( n, m );
	int last, ans = 0;
	for( int i = 1;i <= n;i = last + 1 ) {
		last = min( n / ( n / i ), m / ( m / i ) );
		ans += ( pre[last] - pre[i - 1] ) * ( n / i ) * ( m / i );
	}
	return ans;
}

signed main() {
	int L, R;
	scanf( "%lld %lld", &L, &R );
	init( R );
	int ans = 0;
	for( int k = 2;k <= R;k ++ ) {
		int l = ( L - 1 ) / k, r = R / k;
		ans += calc( r, r ) - calc( l, r ) - calc( r, l ) + calc( l, l );
	}
	for( int i = 2;i <= R;i ++ )
		for( int j = i << 1;j <= R;j += i )
			d[j].push_back( i );
	for( int i = max( 2ll, L );i <= R;i ++ ) {//1压根没有被前面莫比乌斯反演算进去 所以不能枚举 枚举了反而可能会让答案少n
		for( int j = d[i].size() - 1;~ j;j -- )
			if( d[i][j] >= L ) ans --;
			else break;
		ans -= ( R / i );
	}
	printf( "%lld\n", ans );
	return 0;
}

F - Interval Game 2

S G SG SG函数一学就是一万年,嘤嘤嘤(╥╯^╰╥)

直接送个链接

#include <map>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 105
int T, n;
int L[maxn], R[maxn];
int dp[maxn][maxn];

int dfs( int l, int r ) {
	if( l >= r ) return 0;
	if( ~ dp[l][r] ) return dp[l][r];
	map < int, bool > vis;
	for( int i = 1;i <= n;i ++ )
		if( l <= L[i] && R[i] <= r )
			vis[dfs( l, L[i] ) ^ dfs( R[i], r )] = 1;
	for( int i = 0;;i ++ )
		if( ! vis[i] ) return dp[l][r] = i;
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%d %d", &L[i], &R[i] );
		memset( dp, -1, sizeof( dp ) );
		if( dfs( 1, 100 ) ) printf( "Alice\n" );
		else printf( "Bob\n" );
	}
	return 0;
}

ABC-209

C - Not Equal

对于 i , i + 1 i,i+1 i,i+1如果 i + 1 i+1 i+1的上限比 i i i大,那么 i i i无论怎么选, i + 1 i+1 i+1的选择都是 a i + 1 − 1 a_{i+1}-1 ai+11

从这里得到性质,若 [ 1 , i ] [1,i] [1,i]是不下降序列,则 i i i的选择为 a i − ( i − 1 ) a_i-(i-1) ai(i1),求乘积即可

恰好此题只需要排个序就能满足上述性质

#include <cstdio>
#include <algorithm>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 200005
int n, ans;
int c[maxn];

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &c[i] );
	sort( c + 1, c + n + 1 );
	ans = 1;
	for( int i = 1;i <= n;i ++ )
		ans = ans * ( c[i] - i + 1 ) % mod;
	printf( "%lld\n", ans );
	return 0;
}

D - Collision

树,边权相同,相同速度从两个点朝对方移动

完全就是树上 l c a lca lca标配,直接求两个点之间距离的奇偶就知道是在路上还是点上

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 200005
vector < int > G[maxn];
int n, Q;
int dep[maxn];
int f[maxn][20];

void dfs( int u, int fa ) {
	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];
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		if( v == fa ) continue;
		else dfs( v, u );
	}
}

int GetLca( int u, int v ) {
	if( dep[u] < dep[v] ) swap( u, v );
	for( int i = 19;~ i;i -- )
		if( dep[f[u][i]] >= dep[v] )
			u = f[u][i];
	if( u == v ) return u;
	for( int i = 19;~ i;i -- )
		if( f[u][i] != f[v][i] )
			u = f[u][i], v = f[v][i];
	return f[u][0];
}

int main() {
	int n, Q, u, v;
	scanf( "%d %d", &n, &Q );
	for( int i = 1;i < n;i ++ ) {
		scanf( "%d %d", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );
	}
	dfs( 1, 0 );
	while( Q -- ) {
		scanf( "%d %d", &u, &v );
		int lca = GetLca( u, v );
		if( ( dep[u] + dep[v] - ( dep[lca] << 1 ) ) & 1 )
			printf( "Road\n" );
		else
			printf( "Town\n" );
	}
	return 0;
}

E - Shiritori

是个标准的图上博弈问题。

将一个单词看成图上的一条边,首三个字母向末三个字母连一条有向边(哈希或者map编号)

  • 对于没有出边的顶点,状态为N(必胜态)——初始化局面

  • 对于一个没有确定态的顶点

    • 如果有一条出边指向P(必败态),则该顶点为N
    • 如果所有出边都是N,则该顶点为P

用类拓扑方法,确定状态点才会被放进队列,最后若还是未知态则是Draw局面

发现,初始局面确定状态的点进行转移,需要知道其前驱(指向他的多个点)

因此图上博弈问题建的是反图,初始局面的已知态点变成了反图中没有入度的点

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 200005
queue < int > q;
pair < int, int > edge[maxn];
vector < int > G[maxn];
int n;
char s[10];
int ans[maxn], out_d[maxn];

int num( char x ) {
	if( x >= 'A' && x <= 'Z' ) return x - 'A';
	else return x - 'a' + 26;
}

int Hash( char i, char j, char k ) {
	return num( k ) + 52 * num( j ) + 52 * 52 * num( i );
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%s", s + 1 );
		int len = strlen( s + 1 );
		edge[i] = make_pair( Hash( s[1], s[2], s[3] ), Hash( s[len - 2], s[len - 1], s[len] ) );
		out_d[edge[i].first] ++;
		G[edge[i].second].push_back( edge[i].first );
	}
	for( int i = 0;i < 53 * 53 * 53;i ++ )
		if( ! out_d[i] ) {
			q.push( i );
			ans[i] = 1;
		}
	while( ! q.empty() ) {
		int u = q.front(); q.pop();
		for( int i = 0;i < G[u].size();i ++ ) {
			int v = G[u][i];
			if( ! ans[v] ) {
				-- out_d[v];
				if( ans[u] == 1 ) ans[v] = -1, q.push( v );
				else if( ! out_d[v] ) ans[v] = 1, q.push( v );
			}
		}
	}
	for( int i = 1;i <= n;i ++ )
		switch( ans[edge[i].second] ) {
			case 1 : {
				printf( "Takahashi\n" );
				break;
			}
			case -1 : {
				printf( "Aoki\n" );
				break;
			}
			case 0 : {
				printf( "Draw\n" );
				break;
			}
		}
	return 0;
}

F - Deforestation

考虑相邻两棵树 i − 1 , i i-1,i i1,i,如果先砍 i − 1 i-1 i1再砍 i i i,贡献为 h i − 1 + h i + h i h_{i-1}+h_i+h_i hi1+hi+hi;如果先砍 i i i再砍 i − 1 i-1 i1,贡献为 h i + h i − 1 + h i − 1 h_i+h_{i-1}+h_{i-1} hi+hi1+hi1

显然,后砍的树贡献次数要多一次

所以我们贪心地得出先砍较高的树

d p i , j : dp_{i,j}: dpi,j:所有排列中使得 i i i树是第 j j j个被砍掉的数量(插头 d p dp dp

  • h i = h i − 1 h_i=h_{i-1} hi=hi1,则无所谓先后

    d p i , j = ∑ k = 1 i − 1 d p i − 1 , k dp_{i,j}=\sum_{k=1}^{i-1}dp_{i-1,k} dpi,j=k=1i1dpi1,k

  • h i > h i − 1 h_i>h_{i-1} hi>hi1

    d p i , j = ∑ k = j i − 1 d p i − 1 , k dp_{i,j}=\sum_{k=j}^{i-1}dp_{i-1,k} dpi,j=k=ji1dpi1,k

  • h i < h i − 1 h_i<h_{i-1} hi<hi1

    d p i , j = ∑ k = 1 j − 1 d p i − 1 , k dp_{i,j}=\sum_{k=1}^{j-1}dp_{i-1,k} dpi,j=k=1j1dpi1,k

#include <cstdio>
#define maxn 4005
#define int long long
#define mod 1000000007
int n;
int h[maxn];
int dp[maxn][maxn];

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &h[i] );
	h[0] = h[1], dp[0][0] = 1;
	for( int i = 1;i <= n;i ++ ) {
		for( int j = 1;j <= i;j ++ ) {
			if( h[i] == h[i - 1] )
				dp[i][j] = dp[i - 1][i - 1];
			else if( h[i] > h[i - 1] )
				dp[i][j] = ( dp[i - 1][i - 1] - dp[i - 1][j - 1] + mod ) % mod;
			else
				dp[i][j] = dp[i - 1][j - 1];
		}
		for( int j = 1;j <= i;j ++ )
			dp[i][j] = ( dp[i][j] + dp[i][j - 1] ) % mod;
	}
	printf( "%lld\n", dp[n][n] );//已经是dp[n][1~n]的前缀和
	return 0;
}

luogu

LGR-87(Div.2)

远古档案馆

2x2不无脑爆搜才是没有脑子

#include <cstdio>
#include <iostream>
#include <map>
using namespace std;
int s[3][3], t[3][3];
map < int, int > mp;
int End;

int id( int g[3][3] ) {
	int num = 0;
	for( int i = 1;i <= 2;i ++ )
		for( int j = 1;j <= 2;j ++ )
			num = num * 10 + g[i][j];
	return num;
}

void dfs( int g[3][3] ) {
	int now = id( g );
	if( now == End ) {
		printf( "Yes\n" );
		exit( 0 );
	}
	if( mp[now] ) return;
	mp[now] = 1;
	int tmp[3][3] = {};
	for( int i = 1;i <= 2;i ++ )
		for( int j = 1;j <= 2;j ++ )
			tmp[i][j] = g[i][j];
	for( int i = 1;i <= 2;i ++ )
		for( int j = 1;j <= 2;j ++ )
			if( ! tmp[i][j] ) continue;
			else {
				if( i > 1 && ! tmp[i - 1][j] ) {
					swap( tmp[i - 1][j], tmp[i][j] );
					dfs( tmp );
					for( int x = 1;x <= 2;x ++ )
						for( int y = 1;y <= 2;y ++ )
							tmp[x][y] = g[x][y];
				}
				if( j > 1 && ! tmp[i][j - 1] ) {
					swap( tmp[i][j], tmp[i][j - 1] );
					dfs( tmp );
					for( int x = 1;x <= 2;x ++ )
						for( int y = 1;y <= 2;y ++ )
							tmp[x][y] = g[x][y];
				}
				if( i < 2 && ! tmp[i + 1][j] ) {
					swap( tmp[i][j], tmp[i + 1][j] );
					dfs( tmp );
					for( int x = 1;x <= 2;x ++ )
						for( int y = 1;y <= 2;y ++ )
							tmp[x][y] = g[x][y];
				}
				if( j < 2 && ! tmp[i][j + 1] ) {
					swap( tmp[i][j], tmp[i][j + 1] );
					dfs( tmp );
					for( int x = 1;x <= 2;x ++ )
						for( int y = 1;y <= 2;y ++ )
							tmp[x][y] = g[x][y];
				}
			}
}

int main() {
	for( int i = 1;i <= 2;i ++ )
		for( int j = 1;j <= 2;j ++ )
			scanf( "%d", &s[i][j] );
	for( int i = 1;i <= 2;i ++ )
		for( int j = 1;j <= 2;j ++ )
			scanf( "%d", &t[i][j] );
	End = id( t );
	dfs( s );
	printf( "No\n" );
	return 0;
}

珍珠帝王蟹

  • 暴力大讨论多种情况,ε=ε=ε=┏(゜ロ゜;)┛

  • 因为1<|v|,所以肯定是不能放过任何一个乘数的

    • 所有乘数乘积为正

      • 负乘数个数为偶数

        先把加正数的操作做了,再乘所有乘数,最后减去所有负数

      • 负乘数个数为奇数

        先把加正数的操作做了,再乘一个最大的负乘数,然后加上所有负整数操作,最后一起乘剩下乘数

    • 所有乘数乘积为负

      先把负数加在一起,再乘一个最大的负乘数,转化为正数,然后加上所有正数操作,最后乘所有乘数

#include <cstdio>
#define int long long
#define mod 998244353
int n, v, add, add_, mul = 1, t = 1;
char op[5];

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%s %lld", op, &v );
		if( op[0] == '+' )
			if( v > 0 ) add = ( add + v ) % mod;
			else add_ = ( add_ + v ) % mod;
		else 
			if( t > 0 || ( v > t && v < 0 ) ) mul = mul * t % mod, t = v;
			else mul = mul * v % mod;
	}
	if( mul * t > 0 )
		if( t > 0 ) printf( "%lld\n", ( add * mul % mod * t % mod + add_ + mod ) % mod );
		else printf( "%lld\n", ( add * mul % mod * t % mod + add_ * mul % mod + mod ) % mod );
	else
		printf( "%lld\n", ( add_ * mul % mod * t % mod + add * mul % mod + mod ) % mod );
	return 0;
}

天体探测仪

  • ∀ i \forall i i,设 [ l i , r i ] : [l_i,r_i]: [li,ri]: i i i是区间内最小值的最长区间

  • 考虑在 [ l i , r i ] [l_i,r_i] [li,ri]中如何确定 i i i的位置,令 l e n = r i − l i + 1 len=r_i-l_i+1 len=rili+1

    • 对于长度 = j = j =j的区间个数为 l e n − j + 1 len-j+1 lenj+1,若所有区间都包含 i i i,那么 i i i应该出现 l e n − j + 1 len-j+1 lenj+1
    • 找到最大的 j j j,使得 i i i出现次数 < l e n − j + 1 <len-j+1 <lenj+1,此时 i i i的位置可以为 l i + j / r i − j l_i+j/r_i-j li+j/rij
  • 在操作过程中,显然只用到了区间的长度 l e n len len,并不关心区间真正是什么样子

  • 巧合的,发现 l e n len len在输入中就已经给出,最大的 j j j满足 i ∈ S j → l e n = j i∈S_{j}\rightarrow len=j iSjlen=j

  • 用队列 q i q_i qi维护当前长度为 i i i的空区间,队列内的每个元素代表一个区间的左端点

    初始化 q n q_n qn存了 [ 1 , n ] [1,n] [1,n]整个序列,然后从 1 1 1 n n n开始放数

    每次取出 q l e n q_{len} qlen的队头,将区间裂成 l e n ′ = j , l e n ′ ′ = l e n − j − 1 len'=j,len''=len-j-1 len=j,len=lenj1

#include <queue>
#include <cstdio>
using namespace std;
#define maxn 805
queue < int > q[maxn];
int n;
int last[maxn], ans[maxn];
int cnt[maxn][maxn];

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		for( int j = 1, k;j <= n - i + 1;j ++ ) {
			scanf( "%d", &k );
			cnt[k][i] ++, last[k] = i;
		}
	q[n].push( 1 );
	for( int i = 1;i <= n;i ++ ) {
		int pos = 0;
		for( int j = last[i] - 1;j;j -- )
			if( cnt[i][j] < cnt[i][last[i]] + last[i] - j ) {
				pos = j;
				break;
			}
		int l = q[last[i]].front();//last[i]就是len(i) 
		q[last[i]].pop();
		ans[l + pos] = i;
		q[pos].push( l );
		q[last[i] - pos - 1].push( l + pos + 1 );//放的是新区间的左端点L 
	}
	for( int i = 1;i <= n;i ++ )
		printf( "%d ", ans[i] );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值