2020 CCPC Wannafly Winter Camp Day6

Solved:6/14(C、F、K、L、M、N)

Rank:51/124

UpSolving:10/14(A、C、F、G、I、J、K、L、M、N)

由于签到比较多,在迅速的签完 5 题后,rk 最高到了 2nd(第一名是志愿者队)~ ~ ~


A、Convolution

https://blog.csdn.net/qq_41608020/article/details/104348817

C、酒馆战棋

简单贪心

#include
using namespace std;
const int N = 1005;
char s[N];
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n, a, b, c, d;
		scanf("%d %d %d %d %d", &n, &a, &b, &c, &d);
		scanf("%s", s + 1);
		int mx = 0, mn = 0;
		int ta = a, tb = b, tc = c, td = d;
		for (int i = 1; i <= n; i++) {
			if (s[i] == '1') {
				if (c != 0)
					mx++, c--;
				else if (d != 0)
					c++, d--;
				else if (a != 0)
					mx++, a--;
				else if (b != 0)
					b--, a++;
			}
			else {
				if (d != 0)
					c++, d--;
				else if (c != 0)
					;
				else if (b != 0)
					a++, b--;
			}
		}
		a = ta, b = tb, c = tc, d = td;
		for (int i = 1; i <= n; i++) {
			if (s[i] == '1') {
				if (d != 0)
					d--, c++;
				else if (c != 0)
					mn++, c--;
				else if (b != 0)
					b--, a++;
				else if (a != 0)
					mn++, a--;
			}
			else {
				if (c != 0)
					;
				else if (d != 0)
					d--, c++;
				else if (a != 0)
					;
				else if (b != 0)
					b--, a++;
			}
		}
		printf("%d %d\n", mx, mn);
	}
}
/*
1
5 2 2 2 1
10101

*/

F、图与三角形

n个点,一系列操作求出邻接矩阵,每个颜色要么是黑色要么是白色,求同色三角形个数。

考虑求非同色三角形个数,显然,非同色三角形要么是两条黑色边一条白色边要么是两条白色边一条黑色边。

对于非同色三角形的三个点,其中一个点连接的两条边颜色相同,两个点连接的两条边颜色不同。

并且对于两条边颜色不同的点,无论第三条边是什么颜色,它都一定是非同色三角形。

所以我们只需要枚举每个点,看这个点能构成多少个两边颜色不同的三角形,然后将答案除以 2 就可以了。

#include
using namespace std;
typedef long long ll;
ll n;
ll ans=0;
bool book[5005][5005];
ll A,B,C,P,D;
ll black[5005],white[5005];
void solve(){
	scanf("%lld",&n);
	scanf("%lld%lld%lld%lld%lld",&A,&B,&C,&P,&D);
	for(ll i=1;i<=n;++i){
		for(ll j=i+1;j<=n;++j){
			if(i==j) continue;
			if((A*(1ll*i+j)*(1ll*i+j)+B*(1ll*i-j)*(1ll*i-j)+C)%P>D) 
				book[i][j]=book[j][i]=true;
		}
	}
//	for(ll i=1;i<=n;++i){
//		for(ll j=1;j<=n;++j){
//			printf("%d",book[i][j]);
//		}
//		printf("\n");
//	}
	for(ll i=1;i<=n;++i){
		for(ll j=i+1;j<=n;++j){
			if(book[i][j]){
				++black[i];
				++black[j];
			}
			else{
				++white[i];
				++white[j];
			}
		}
	}
	ll ans = 1ll*n*(n-1)*(n-2)/6;
	ll sum=0;
	for(ll i=1;i<=n;++i){
		sum+=black[i]*white[i];
	}
	ans-=sum/2;
	printf("%lld\n",ans);
}
int main(void)
{
	solve();
	return 0;
}

G、单调栈

每个值表示  \sum_{i=1}^{k-1}[a_i<a_k]\ +\ 1 ,即左边的比他小的数字个数 + 1,显然第一个数字一定是 1, 然后后面所有是 1 的数字考虑从后向前放数字,这样可以满足所有值为 1 的位置,然后递归向下即可。

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
int a[105], cnt, ans[105], n;
void dfs(int a[], int pos, int index)
{
	if (pos > n)
		return;
	for (int i = n; i > pos; i--)
	{
		if (a[i] == index)
			ans[i] = cnt++;
	}
	ans[pos] = cnt++;
	for (int i = pos + 1; i <= n; i++)
	{
		if (ans[i] == 0)
			dfs(a, i, index + 1);
	}
}
int main()
{
	int T;
	sc("%d", &T);
	while (T--)
	{
		memset(ans, 0, sizeof(ans));
		cnt = 1;
		sc("%d", &n);
		for (int i = 1; i <= n; i++)
			sc("%d", &a[i]);
		dfs(a, 1, 1);
		for (int i = 1; i <= n; i++)
			pr("%d%c", ans[i], i == n ? '\n' : ' ');
	}
}

I、变大!

p[x][y] x表示第x个元素 y表示到第y个元素的时候分y段
dp[x][y] = max(dp[x-len][y-k]+mx*len,dp[x][y])
所以枚举k,操作次数为k的时候最长的区间长度是2*k+1,最短的区间长度是2*k,当然要判断k等于1的时候最短的区间长度是3。mx就是区间里面的最大值

#include
using namespace std;
#define ll long long
const int N = 55;
int c[N], dp[55][55];
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n;
		scanf("%d", &n);
		dp[0][0] = 0;
		for (int i = 1; i <= n; i++)
			scanf("%d", &c[i]);
		for (int i = 0; i <= n; i++)
			for (int j = 0; j <= n; j++)
				dp[i][j] = 0;
		for (int i = 1; i <= n; i++) {
			for (int j = 0; j <= n; j++)
				dp[i][j] = max(dp[i - 1][j] + c[i], dp[i][j]);
			for (int k = 1; k <= n; k++) {
				int mn = k * 2 == 2 ? 3 : k * 2, mx = k * 2 + 1;
				if (i - mn >= 0) {
					int mxx = 0;
					for (int j = i - mn + 1; j <= i; j++)
						mxx = max(mxx, c[j]);
					for (int j = k; j <= n; j++)
						dp[i][j] = max(dp[i-mn][j - k] + mxx * mn,dp[i][j]);
				}
				if (i - mx >= 0) {
					int mxx = 0;
					for (int j = i - mx + 1; j <= i; j++)
						mxx = max(mxx, c[j]);
					for (int j = k; j <= n; j++)
						dp[i][j] = max(dp[i-mx][j - k] + mxx * mx, dp[i][j]);
				}
			}
		}
		for (int i = 1; i <= n; i++)
			printf(i == n ? "%d\n" : "%d ", dp[n][i]);
	}
}

J、K重排列

假设原排列是由几个数字循环构成的,显然每个数字循环的最小循环节一定要是 k 的因数。(因为 k 是整个排列的最小循环节)

枚举位置 i 在长度为 j 的环上,并且选出 j - 1 个位置对其排列,其他数字的排列方式直接转移过来即可。

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
const int MAXN = 55;
ll fac[MAXN], inv[MAXN];
ll dp[MAXN];
const ll mod = 998244353;
void init()
{
	fac[0] = fac[1] = inv[0] = inv[1] = 1;
	for (int i = 2; i < MAXN; i++)
	{
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	for (int i = 2; i < MAXN; i++)
		inv[i] = inv[i] * inv[i - 1] % mod;
}
ll power(ll a, ll b)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)
			res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
ll C(ll n, ll m)
{
	ll ans = fac[n] * inv[m] % mod * inv[n - m] % mod;
	return ans;
}
int main()
{
	init();
	int T;
	sc("%d", &T);
	while (T--)
	{
		ll n, k;
		sc("%lld%lld", &n, &k);
		memset(dp, 0, sizeof(dp));
		dp[0] = 1;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= i; j++)//枚举位置i在长度为j的环上,并且选出j-1个位置对其排列
				if (k % j == 0)
					dp[i] = (dp[i] + dp[i - j] * C(i - 1, j - 1) % mod * fac[j - 1] % mod) % mod;
		pr("%lld\n", dp[n]);
	}
}

四个签到题略

你可能感兴趣的:(2019,Winter,Camp)