NOMURA Programming Contest 2021(AtCoder Regular Contest 121)


NOMURA Programming Contest 2021(AtCoder Regular Contest 121)

A - 2nd Greatest Distance

大模拟讨论yyds

将点按 x , y x,y x,y分别排序

  • x x x贡献最大值的点对等于 y y y贡献最大值的点对

    次小值就变成 x / y x/y x/y中最大次小和次大最小一共四种组合中的最大值

  • x x x最大最小贡献点对不等于 y y y最大最小点对

    • x x x最大最小更大

      y y y最大最小与 x x x中次大最小和最大次小比较

    • y y y最大最小更大

      x x x最大最小与 y y y中次大最小和最大次小比较

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 200005
struct node {
	int x, y, id;
}h1[maxn], h2[maxn];
int n;

bool cmp1( node s, node t ) {
	return s.x < t.x;
}

bool cmp2( node s, node t ) {
	return s.y < t.y;
}

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%lld %lld", &h1[i].x, &h1[i].y );
		h1[i].id = i;
		h2[i] = h1[i];
	}
	sort( h1 + 1, h1 + n + 1, cmp1 );
	sort( h2 + 1, h2 + n + 1, cmp2 );
	if( ( h1[1].id == h2[1].id && h1[n].id == h2[n].id ) || ( h1[1].id == h2[n].id && h1[n].id == h2[1].id ) )
		printf( "%lld\n", max( max( h1[n].x - h1[2].x, h1[n - 1].x - h1[1].x ), max( h2[n].y - h2[2].y, h2[n - 1].y - h2[1].y ) ) );
	else if( h1[n].x - h1[1].x < h2[n].y - h2[1].y )
			printf( "%lld\n", max( h1[n].x - h1[1].x, max( h2[n].y - h2[2].y, h2[n - 1].y - h2[1].y ) ) );
		else
			printf( "%lld\n", max( h2[n].y - h2[1].y, max( h1[n].x - h1[2].x, h1[n - 1].x - h1[1].x ) ) );
	return 0;
}

B - RGB Matching

显然,最后要么三种颜色的狗全是偶数条,两两配对,答案为 0 0 0;要么只会恰有两种颜色的狗为奇数条

case 1

两种颜色中选择不满意值相差最小的两条狗

具体来说,枚举一种颜色的狗,再二分左右求出与该狗不满意值差距最小的狗,整体不满意值取最小值

case 2

第三颜色狗起一个中转点的作用

具体来说,分别枚举两种颜色的狗,以及在第三颜色中求出与枚举狗不满意值差距最小的狗

整体取不满意值最小,两种颜色最小再相加

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define int long long
vector < int > G[3];
int n, pos_l, pos_r;

int id( char ch ) {
	if( ch == 'R' ) return 0;
	if( ch == 'B' ) return 1;
	if( ch == 'G' ) return 2;
}

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

void work( int c, int val ) {
	int l = 0, r = G[c].size() - 1;
	pos_l = 0, pos_r = G[c].size() - 1;
	while( l <= r ) {
		int mid = ( l + r ) >> 1;
		if( G[c][mid] <= val ) pos_l = mid, l = mid + 1;
		else r = mid - 1;
	}
	l = 0, r = G[c].size() - 1;
	while( l <= r ) {
		int mid = ( l + r ) >> 1;
		if( G[c][mid] >= val ) pos_r = mid, r = mid - 1;
		else l = mid + 1;
	}
}

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= ( n << 1 );i ++ ) {
		char c; int a;
		scanf( "%lld %c", &a, &c );
		G[id( c )].push_back( a );
	}
	int c1 = -1, c2 = -1;
	for( int i = 0;i < 3;i ++ )
		if( G[i].size() & 1 ) {
			if( ~ c1 ) c2 = i;
			else c1 = i;
		}
	if( c1 == -1 ) return ! printf( "0\n" );
	else {
		int ans = 1e18;
		sort( G[c2].begin(), G[c2].end() );
		for( int i = 0;i < G[c1].size();i ++ ) {
			work( c2, G[c1][i] );
			ans = min( ans, min( Fabs( G[c1][i] - G[c2][pos_l] ), Fabs( G[c2][pos_r] - G[c1][i] ) ) );
		}
		int ans1 = 1e18, ans2 = 1e18, c;
		for( int i = 0;i < 3;i ++ )
			if( c1 == i || c2 == i ) continue;
			else c = i;
		if( ! G[c].size() ) goto pass;
		sort( G[c].begin(), G[c].end() );
		for( int i = 0;i < G[c1].size();i ++ ) {
			work( c, G[c1][i] );
			ans1 = min( ans1, min( Fabs( G[c1][i] - G[c][pos_l] ), Fabs( G[c][pos_r] - G[c1][i] ) ) );
		}
		for( int i = 0;i < G[c2].size();i ++ ) {
			work( c, G[c2][i] );
			ans2 = min( ans2, min( Fabs( G[c2][i] - G[c][pos_l] ), Fabs( G[c][pos_r] - G[c2][i] ) ) );
		}
		pass : printf( "%lld\n", min( ans, ans1 + ans2 ) );
	}
	return 0;
}

C - Odd Even Sort

题读错做法千奇百怪错 奇数次操作只能操作奇数位置,偶数次操作只能操作偶数位置

问题不要求操作最小,只求不超过 n 2 n^2 n2(冒泡排序复杂度),想法一下子就来了,明显的构造

从最大值往最小值依次考虑( k k k),也就是只用考虑右移的操作

不难发现,如果 k k k本身处于位置与操作次数恰好同奇偶,那么可以直接一直操作 k k k直到位置 k k k

如果不同奇偶,那么就像能否先操作一次无关的位置,再一直操作 k k k

这显然是可以的,每次奇偶就让 1 , 2 1,2 1,2位置与后面一个进行交换

当然还有位置与值已经匹配的,直接下一个哈

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

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%d", &a[i] ), b[i] = a[i];
		sort( b + 1, b + n + 1 );
		int cnt = 0, ip = n;
		for( int t = 1;t <= n * n && ip;t ++ ) {
			bool flag = 0;
			for( int i = 1;i < n;i ++ )
				if( a[i] > a[i + 1] ) {
					flag = 1;
					break;
				}
			if( ! flag ) break;
			int pos;
			for( int i = 1;i <= n;i ++ )
				if( a[i] == b[ip] ) {
					pos = i;
					break;
				}
			if( ( pos & 1 ) == ( t & 1 ) ) {
				while( pos < ip ) {
					ans[++ cnt] = pos;
					t ++;
					swap( a[pos], a[pos + 1] );
					pos ++;
				}
				t --;
				ip --;
			}
			else {
				flag = 1;
				for( int i = ( ( t & 1 ) ? 1 : 2 );i < n;i += 2 )
					if( a[i] > a[i + 1] ) {
						ans[++ cnt] = i;
						swap( a[i], a[i + 1] );
						flag = 0;
						break;
					}
				if( flag ) {
					int x = ( ( t & 1 ) ? 1 : 2 );
					ans[++ cnt] = x;
					swap( a[x], a[x + 1] );
				}
			}
		}
		printf( "%d\n", cnt );
		if( cnt ) {
			for( int i = 1;i <= cnt;i ++ )
				printf( "%d ", ans[i] );
			printf( "\n" );
		}
	}
	return 0;
}

D - 1 or 2

肯定是最大最小,次大次小…一一配对

a < b < c < d ⇒ m a x ( a + d , b + c ) ≤ m a x ( a + c , b + d ) ; m i n ( a + c , b + d ) ≤ m i n ( a + d , b + c ) a<b<c<d\Rightarrow max(a+d,b+c)\le max(a+c,b+d);min(a+c,b+d)\le min(a+d,b+c) a<b<c<dmax(a+d,b+c)max(a+c,b+d);min(a+c,b+d)min(a+d,b+c)

如果是一个蛋糕,不妨看做和 0 0 0合并,所以只需要枚举 0 0 0的个数排序顺次合并即可, O ( n 2 ) O(n^2) O(n2)

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 5005
#define int long long
vector < int > g;
int n;

signed main() {
	scanf( "%lld", &n );
	for( int i = 1, x;i <= n;i ++ ) {
		scanf( "%lld", &x );
		g.push_back( x );
	}
	sort( g.begin(), g.end() );
	int Size = g.size(), maxx = -1e18, minn = 1e18;
	for( int i = 0;i < ( Size >> 1 );i ++ ) {
		maxx = max( g[Size - 1 - i] + g[i], maxx );
		minn = min( g[Size - 1 - i] + g[i], minn );
	}
	if( Size & 1 ) {
		maxx = max( g[Size >> 1], maxx );
		minn = min( g[Size >> 1], minn );
	}
	int ans = maxx - minn;
	for( int i = 1;i <= n;i ++ ) {
		g.push_back( 0 ), Size ++;
		for( int i = Size - 1;i;i -- )
			if( g[i] < g[i - 1] ) swap( g[i], g[i - 1] );
			else break;
		maxx = -1e18, minn = 1e18;
		for( int i = 0;i < ( Size >> 1 );i ++ ) {
			maxx = max( g[Size - 1 - i] + g[i], maxx );
			minn = min( g[Size - 1 - i] + g[i], minn );
		}
		if( Size & 1 ) {
			maxx = max( g[Size >> 1], maxx );
			minn = min( g[Size >> 1], minn );
		}
		ans = min( ans, maxx - minn );
	}
	printf( "%lld\n", ans );
	return 0;
} 

E - Directed Tree

套路都见过,却没有反应过来

d p i , j : i dp_{i,j}:i dpi,j:i 子树内不满足条件的节点数为 j j j

d p u , j + k = ∑ j = 0 s i z e u ∑ k = 0 s i z e v d p u , j × d p v , k dp_{u,j+k}=\sum_{j=0}^{size_u}\sum_{k=0}^{size_v}dp_{u,j}\times dp_{v,k} dpu,j+k=j=0sizeuk=0sizevdpu,j×dpv,k

最后容斥

a n s = ∑ i = 0 n ( − 1 ) i d p 1 , i × ( n − i ) ! ans=\sum_{i=0}^n(-1)^idp_{1,i}\times (n-i)! ans=i=0n(1)idp1,i×(ni)!

#include <cstdio>
#include <vector>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 2005
vector < int > G[maxn];
int n;
int fac[maxn], siz[maxn], g[maxn];
int dp[maxn][maxn];

void dfs( int u ) {
	dp[u][0] = 1;
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		dfs( v );
		for( int j = 0;j <= siz[u];j ++ )
			for( int k = 0;k <= siz[v];k ++ )
				g[j + k] = ( g[j + k] + dp[u][j] * dp[v][k] % mod ) % mod;
		siz[u] += siz[v];
		for( int j = 0;j <= siz[u];j ++ )
			dp[u][j] = g[j], g[j] = 0;
	}
	for( int i = siz[u];~ i;i -- )
		dp[u][i + 1] = ( dp[u][i + 1] + dp[u][i] * ( siz[u] - i ) % mod ) % mod;
	siz[u] ++;
}

signed main() {
	scanf( "%lld", &n );
	for( int i = 2, p;i <= n;i ++ ) {
		scanf( "%lld", &p );
		G[p].push_back( i );
	}
	dfs( 1 );
	fac[0] = 1;
	for( int i = 1;i <= n;i ++ )
		fac[i] = fac[i - 1] * i % mod;
	int ans = 0;
	for( int i = 0;i <= n;i ++ )
		if( i & 1 ) ans = ( ans - dp[1][i] * fac[n - i] % mod + mod ) % mod;
		else ans = ( ans + dp[1][i] * fac[n - i] % mod ) % mod;
	printf( "%lld\n", ans );
	return 0;
}

F - Logical Operations on Tree

N = 1 N=1 N=1结果显然,考虑其他情况

如果一个叶子标记为1并且与之相连的边是OR

不管其它长什么样都可以将这条边的操作放在最后一步,从而符合要求,简称好树

接下来考虑没有这种叶子,合并出新点的情况

  • 如果叶子u的标记为0,且相连边是AND,则这条边操作后,新点一定标记为0

    如果执行这条边后的树是好树,那么原来的树也一定是好树

  • 如果叶子u的标记为0,且相连边是OR,则这条边操作后,原来与叶子相连点的标记即为新点标记

    如果执行这条边后的树是好树,那么原来的树也一定是好树

  • 如果叶子u的标记为1,且相连边是AND,则这条边操作后,原来与叶子相连点的标记即为新点标记

    如果执行这条边后的树是好树,那么原来的树也一定是好树

在树上从叶子到父亲搭建,出现1 OR时剩下的点边不管是什么,一定也是好树了;否则把叶子扔掉,父亲节点可能为0/1,在树上 D P DP DP

f : f: f: 子树内没有出现1 OR想要边的树个数,$g: $ 在 f f f所有方案数中好树的个数

ps:没有出现1 OR这种关键叶子标记1指向父亲种类,但是可以出现1 OR父亲标记1的情况,因为可能父亲的其它儿子操作时会导致父亲标记变化

#include <cstdio>
#include <vector>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 100005
vector < int > G[maxn];
int n;
int f[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;
}

void dfs( int u, int fa ) {
	f[u] = 2, g[u] = 1;
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		if( v == fa ) continue;
		else dfs( v, u );
		f[u] = ( ( f[v] * 2 - g[v] ) * f[u] % mod + mod ) % mod;
		g[u] = g[u] * f[v] % mod;
	}
}

signed main() {
	scanf( "%lld", &n );
	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", ( qkpow( 2, n * 2 - 1 ) - f[1] + g[1] + mod ) % mod );
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值