AtCoder Beginner Contest 220

AtCoder Beginner Contest 220

D - FG operation

题目

n n n个数,每次可以进行两种操作:

  1. 取出前两个数,相加后对 10 10 10取模并将结果放入数列最左端
  2. 取出前两个数,相乘后对 10 10 10取模并将结果放入数列最左端

问,有多少种方案,使得最终结果等于 k k k, k k k取0到9的整数.

思路

明显的DP,设 f i , j f_{i,j} fi,j表示消除前 i i i个数后最前端的数为 j j j的方案数.

f i + 1 , j ⋅ a i % 10 = f i + 1 , j ⋅ a i % 10 + f i , j f_{i+1,j\cdot a_i \%10}=f_{i+1,j\cdot a_i \%10}+f_{i,j} fi+1,jai%10=fi+1,jai%10+fi,j

f i + 1 , ( j + a i ) % 10 = f i + 1 , ( j + a i ) % 10 + f i , j f_{i+1,(j+a_i)\%10}=f_{i+1,(j+a_i)\%10}+f_{i,j} fi+1,(j+ai)%10=fi+1,(j+ai)%10+fi,j

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}

const int mod = 998244353;
const int N = 100010;
int n , a[N];
int f[N][12];
signed main() {
	n = read();
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();
	
	++f[2][(a[1] + a[2]) % 10];
	++f[2][(a[1] * a[2]) % 10];
	for(int i = 2 ; i < n ; i++)
		for(int j = 0 ; j < 10 ; j++) {
			f[i + 1][(j + a[i + 1]) % 10] += f[i][j] , f[i + 1][(j + a[i + 1]) % 10] %= mod;
			f[i + 1][(j * a[i + 1]) % 10] += f[i][j] , f[i + 1][(j * a[i + 1]) % 10] %= mod;
			
		}
	for(int i = 0 ; i < 10 ; i++)
		cout << f[n][i] << endl;
	return 0;
}

E - Distance on Large Perfect Binary Tree

题目

思路

应该是前六题中最难的一道题.

看下数据范围,大概是 O ( n ) O(n) O(n)的.

所以,我们可以一层一层算,设第 i i i层的一个点对答案的贡献为 f ( i ) f(i) f(i)则答案就是 ∑ i = 1 n f ( i ) ⋅ 2 i − 1 \sum^n_{i=1}f(i)\cdot 2^{i-1} i=1nf(i)2i1.

问题就是计算 f ( i ) f(i) f(i).

分三种情况:直接向下,直接向上,先向上后向下.

对于第一种情况,如果 i i i到二叉树底的距离足够大,产生的贡献就是 2 d 2^{d} 2d,否则没有贡献(可以到达以绿框二叉树的任意一个叶子).

对于第二种情况,如果 i i i的深度足够大,产生的贡献就是 1 1 1,否则没有贡献.

//预处理2的次方.
	p[0] = 1;
	for(int i = 1 ; i <= n ; i++)
		p[i] = (p[i - 1] << 1) % mod;
//在for循环中.
		if(n - i >= d)
			tmp += p[d];
		if(i > d)
			++tmp;

对于第三种情况:

如果到树底的距离足够大,我们可以向上一步然后向下,贡献就是 2 h − 1 = 2 d − 2 2^{h-1}=2^{d-2} 2h1=2d2.

如果深度足够大,贡献就是 2 h − 1 = 1 2^{h-1}=1 2h1=1.

除外,我们还可以向上走 2 2 2步, 3 3 3 ⋯ \cdots 再向下走.

贡献就是 ∑ i = 0 d − 2 2 i \sum^{d-2}_{i=0}2^i i=0d22i.

那如果距离不够大呢?

对于右图,容易得到, d − i < 0 d - i < 0 di<0时,深度足够,否则,绿框二叉树的深度最小值为 d − i + 1 d - i+1 di+1,贡献是 2 d − i 2^{d-i} 2di.

对于左图,我们设向上走到 j j j深度再向下走可以满足条件,则有 ( i − j ) + ( n − j ) ≥ d (i-j)+(n-j)\ge d (ij)+(nj)d,即 j ≤ i + n − d 2 j\le \frac{i+n-d}2 j2i+nd, j j j的最大值就是 ⌊ i + n − d 2 ⌋ \lfloor \frac{i+n-d}2\rfloor 2i+nd,绿框二叉树的深度也就是 d − ( i − j ) d-(i-j) d(ij).

所以我们定义一个 l , r l,r l,r.

		int l = (d - i < 0 ? 0 : d - i);
		int r = (n - i >= d - 2 ? d - 2 : d - (i - (i + n - d) / 2)) - 1;

答案就是 ∑ i = 1 r 2 i \sum^{r}_{i=1}2^i i=1r2i.根据等比数列,答案就是
2 r + 1 − 2 l 2^{r+1}-2^l 2r+12l

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}

const int mod = 998244353;
int p[1000010];
int n , d;
signed main() {
	n = read() , d = read();
	
	p[0] = 1;
	for(int i = 1 ; i <= n ; i++)
		p[i] = (p[i - 1] << 1) % mod;
	
	int ans = 0;
	for(int i = 1 ; i <= n ; i++) {
		int tmp = 0;
		if(n - i >= d)
			tmp += p[d];
		if(i > d)
			++tmp;
		int l = (d - i < 0 ? 0 : d - i);
		int r = (n - i >= d - 2 ? d - 2 : d - 1 - (i - (i + n - d) / 2));
		if(l <= r) {
			tmp += (p[r + 1] - p[l] + mod) % mod;
		}
		tmp %= mod;
		ans = (ans + tmp * p[i - 1]) % mod;
		
//		printf("%lld:\t%lld\n" , i , tmp);
	}
	cout << ans % mod;
	return 0;
}

F - Distance Sums 2

题目

思路

简单的树形DP,设 f i f_i fi表示其他点到 i i i点的距离之和.

则有:
f i = f j − s i z e ( i ) + ( n − s i z e ( i ) ) f_i=f_j-size(i)+(n-size(i)) fi=fjsize(i)+(nsize(i))
其中, j j j i i i的父节点, s i z e ( i ) size(i) size(i)表示以 i i i为根的子树大小,方程很直观.

特别地,根节点的 f f f是所有节点的深度之和(深度从0开始).

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}

const int N = 200010;

struct EDGE {
	int to , nxt;
}ed[N * 2];
int head[N];
void addedge(int u , int v) {
	static int cnt;
	++cnt;
	ed[cnt].to = v , ed[cnt].nxt = head[u] , head[u] = cnt;
}


int n;
int dep[N];
int siz[N];

int f[N];

void dfs(int x , int fa) {
	siz[x] = 1;
	dep[x] = dep[fa] + 1;
	for(int i = head[x] ; i ; i = ed[i].nxt) {
		if(ed[i].to != fa)
			dfs(ed[i].to , x) , siz[x] += siz[ed[i].to];
	}
}


void dfs2(int x , int fa) {
	if(x != 1)
		f[x] = f[fa] + n - siz[x] - siz[x];
	for(int i = head[x] ; i ; i = ed[i].nxt) {
		if(ed[i].to != fa)
			dfs2(ed[i].to , x);
	}
}
signed main() {
	n = read();
	for(int i = 1 ; i < n ; i++) {
		int u = read() , v = read();
		addedge(u , v) , addedge(v , u);
	}
	
	dep[0] = -1;
	dfs(1 , 0);
	for(int i = 1 ; i <= n ; i++)
		f[1] += dep[i];
	
	dfs2(1 , 0);
	for(int i = 1 ; i <= n ; i++) {
		printf("%lld\n" , f[i]);
	}
	return 0;
}

G - Isosceles Trapezium

题目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmI7Hsqp-1632837178327)([ABC220]AtCoder Beginner Contest 220.assets/image-20210928214139088.png)]

思路

四个点能构成等腰梯形,当且仅当两点的中垂线与另外两点的中垂线重合,且中点不重合.

然后大水题,STL乱搞.

代码

#include <bits/stdc++.h>
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' || c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return negt ? -re : re;
}
const int N = 1010;
typedef long long ll;
typedef pair<pair<ll , ll> , ll> line;
typedef pair<ll , ll> point;
line makeline(ll A , ll B , ll C) {
	return make_pair(make_pair(A , B) , C);
}
ll gcd(ll a , ll b) {
	return b == 0 ? a : gcd(b , a % b);
}

int n;
ll x[N] , y[N];
ll w[N];

#define index index_
map <line , int> index;
map <point , ll> a[N * N];
int cnt;
int main() {
	n = read();
	for(int i = 1 ; i <= n ; i++)
		x[i] = read() * 2 , y[i] = read() * 2 , w[i] = read();
	for(int i = 1 ; i <= n ; i++)
		for(int j = i + 1 ; j <= n ; j++) {
			point mid;
			mid.first = (x[i] + x[j]) / 2 , mid.second = (y[i] + y[j]) / 2;
			ll A , B , C;//直线一般式方程,防止恶心的浮点数(精度问题很麻烦),也防止斜率不存在的尴尬情况.
			ll delx = x[i] - x[j];
			ll dely = y[i] - y[j];
			ll sumw = w[i] + w[j];
			if(delx == 0)
				A = 0 , B = 1 , C = -(y[i] + y[j]) / 2;
			else if(dely == 0)
				A = 1 , B = 0 , C = -(x[i] + x[j]) / 2;
			else {
				A = delx , B = dely , C = -dely * mid.second - delx * mid.first;
				ll g = (C == 0 ? gcd(A , B) : gcd(gcd(A , B) , C));//化简,保证重合的直线写出来的方程一致
				A /= g , B /= g , C /= g;
				if(A < 0)
					A = -A , B = -B , C = -C;
			}
			line l = makeline(A , B , C);
//			printf("%lld\t%lld\t%lld:\t%lld\n" , A , B , C , sumw);
			int id;
			if(index.find(l) == index.end())
				index[l] = id = ++cnt;
			else
				id = index[l];
			
			if(a[id].find(mid) == a[id].end())
				a[id][mid] = sumw;
			else
				a[id][mid] = max(a[id][mid] , sumw);
		}
	
	ll ans = -1;
	for(int i = 1 ; i <= cnt ; i++) {
		ll max1 = -1 , max2 = -1;
		for(auto j = a[i].begin() ; j != a[i].end() ; j++) {
			ll val = j->second;
			if(val > max1)
				max2 = max1 , max1 = val;
			else if(val > max2)
				max2 = val;
		}
		if(max1 != -1 && max2 != -1 && ans < max1 + max2)
			ans = max1 + max2;
	}
	cout << ans;
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值