周总结 2022-02-07 ~ 2022-02-13 咕咕咕

2月7日到2月10日的太远(lan)不整了(小声)

2月11日

Codeforces Hello 2022

C

交互题,一开始有一个数组p,和一个数组q
数组q最开始是[1,2,3,4...]此类形式的
然后p是你要猜的,最多进行2 * n次的询问
每次询问可以得到当前p数组中某一个下标的值
然后会对q数组进行一次变换,即qi = q[p[i]]
具体的看样例吧...
很容易发现,经过若干次变换之后,数组会变成最开始的[1,2,3,4...]此类
然后会发现,其实其中的每个数据会出现一种环,然后数字一直在这种环中进行交换,而不会和环外(环中没出现的数字)交换,例如:
2 3 1,构成了一个长度为3的环,此时我们对其进行询问最多询问环长+1次即可得到环中所有的数字
得到的数字以后,我们并不知道它在原数组的什么位置,但是通过验算可知,后一个数字存在的下标就是前一个数字
例如2 3 1,那么3就会在i = 2的位置1就在i = 3的位置,2就会在i = 1的位置
AC代码:

点击查看代码
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1e4 + 7;
int ans[MAXN];
int get(int x)
{
	cout << "? " << x << "\n";
	cin >> x;
	return x;
}
bool vis[MAXN];
void solve()
{
	int n;
	cin >> n;
	if(n == 1)
	{
		cout << "! 1\n";
		return ;
	}
	for(int i = 1;i <= n;++i)	ans[i] = -1,vis[i] = 0;
	for(int i = 1;i <= n;++i)
	{
		if(ans[i] == -1)
		{
			int ls = get(i);
			vector<int> c;
			while(!vis[ls])
			{
				vis[ls] = 1;
				c.push_back(ls);
				ls = get(i);
			}
			c.push_back(ls);
			for(int i = 1;i < c.size();++i)
			ans[c[i - 1]] = c[i];
		}
	}
	cout << "!";
	for(int i = 1;i <= n;++i)
	cout << " " << ans[i];
	cout << "\n";
//	printf("%d%c",ans[i]," \n"[i == n]);
}
int main()
{
	int t;
	cin >> t;
	while(t--)
	solve();
	return 0;
}

D

555,想成前缀和的解法了。。。题目没看清楚
每次对其中一列进行列移动,或者行进行行移动,使得所有左上角的人都去右下角,求最小花费
答案就是[1,n + 1],[1,2 * n],[n,n + 1],[n,2 * n],[n + 1,1],[n + 1,n],[2 * n,1],[2 * n,n]花费的最小值+右下角的n*n中的花费,可以证明其中所有的人都可以从这8个口中进入到右下角
AC代码

点击查看代码
#include <iostream> 
using namespace std;
typedef long long ll;
const int MAXN = 505;
ll a[MAXN][MAXN];
ll s1[MAXN][MAXN],s2[MAXN][MAXN];
void solve()
{
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n * 2;++i)
	for(int j = 1;j <= n * 2;++j)	scanf("%lld",&a[i][j]);
	ll ans = 0;
//	for(int i = 1;i <= n;++i)
//	for(int j = 1;j <= n;++j)	ans += a[i][j];
	for(int i = n + 1;i <= n * 2;++i)
	for(int j = n + 1;j <= n * 2;++j)	ans += a[i][j];
	
	ll Min = 1e18;
	Min = min(min(a[1][n + 1],a[1][2 * n]),min(a[n][n + 1],a[n][n * 2]));
	Min = min(Min,min(a[n + 1][1],a[n + 1][n]));
	Min = min(Min,min(a[2 * n][1],a[2 * n][n]));
	printf("%lld\n",ans + Min);
}
/*
1
3
0 0 0 100 0 100
0 0 0 0 0 0 
0 0 0 100 0 100
100 0 100 0 0 0
0 0 0 0 0 0
100 0 100 0 0 0
*/
int main()
{
//	freopen("out.txt","r",stdin);
	int t;
	scanf("%d",&t);
	while(t--)
	solve();
	return 0;
}

2月12日

牛客寒假训练营六

B

题意

一个数组的价值是绝对值的差分和,给定一个长度为n的数组,求其中有多少子序列的差分和为整个序列的差分和,答案对998244353取模

题解

观察差分的序列
先看特殊情况,如果序列是连续递增(或者连续递减的)的,那么我们必拿走首尾的两个数字。
因为:
对于差分序列而言,此题整个序列的Value就是差分序列的绝对值的和
如果只拿走首尾的字符,那么对于差分序列而言,不就是求了一个[2,n]的前缀和(其中第1个字符时和0进行差分的..实际上没有作用)。
结论:
那么我们可以对一段连续或者一段递减的序列看成一个连通块,我们只需要拿走连通块的首尾两个字符即可,其中的任何数字随便拿。
但是对于一串连续相同的区间,我们难以进行判断,这时候需要想一个更加简单的方式,既然是一段连续的区间,我们只需要拿走首尾两个,那么我们只需要判断当前第i位的数字和前后两位的数字进行判定(首先对序列的重复数字进行去除,然后判断重复序列的左右),如果是有序的,那么当前长度为Len的相同数字就可以随便拿,当然Len很可能等于1,如果不能判断(即当前i为首或者尾),那么这个数字就必拿Len中的一个。
AC代码:

点击查看代码
#include <iostream>
#include <map>
#include <vector>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 7;
const int MOD = 998244353;
map<int,int> dp[MAXN];
ll a[MAXN];
/*
1
1
1
*/
ll dfs(int le,int ri,ll tar)
{
	if(tar < 0)	return 0;
	if(le == ri)	return tar == 0;
	if(dp[ri].count(tar))	return dp[ri][tar];
	
	ll ans = 0;
	for(int i = ri - 1;i >= le;--i)
	{
		if(i == 0)
		ans += tar == 0;
		else
		ans += dfs(0,i,tar - abs(a[ri] - a[i]));
		ans %= MOD;
	}
	return dp[ri][tar] = ans;
}
bool vis[MAXN];
ll ksm(ll a,ll b)
{
	ll t = 1;
	while(b)
	{
		if(b & 1)	t = t * a % MOD;
		b >>= 1;
		a = a * a % MOD;
	}return t % MOD;
} 
/*
1
6
6 6 6 6 6 6
*/
void solve()
{
	int n;
	scanf("%d",&n);
	for(int i = 0;i <= n;++i)	a[i] = 0,vis[i] = 0;
	
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]);
	
	int cnt = 1;
	
	ll ans = 1;
	for(int i = 1;i <= n;++i)
	{
		int j = i;
		while(j <= n && a[j] == a[i])	j += 1;
		
		int len = j - i;
		if(i >= 2 && j <= n && (a[i - 1] > a[i] && a[j - 1] > a[j]\
		 || a[i - 1] < a[i] && a[j - 1] < a[j]))	
		 ans = ans * ksm(2,len) % MOD;
		 else
		ans = ans * (ksm(2,len) - 1 + MOD) % MOD;
		
		i = j - 1;
	}
	printf("%lld\n",ans);
}
int main()
{
//	freopen("out.txt","r",stdin);
	int t;
	scanf("%d",&t);
	while(t--)
	solve();
	return 0;
}

J

知识点:线性逆元
(不知道线性逆元TLE到爆炸
注意题目的Ai的范围只有[0,2]
那么我们可以对1 2 的数字个数进行枚举,这一题就变成了一个组合数学的题目...
关键点:线性逆元
递推来源:
设t = m / i,k = m % i
那么有t * i + k = 0(mod m)
移项:-t * i = k(mod m)
两边同时除以 i * k,得到:
-t * inv[k] = inv[i](mod m)
即:
-m / i * inv[m % i] = inv[i] % m
然后就是简单的排列组合咯...
AC代码:

点击查看代码
#include <iostream>
#include <cmath>
//#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int MOD = 998244353;
const int inf = 1e9 + 7;
const int MAXN = 10000001;
ll ksm(ll a,ll b)
{
	ll t = 1;
	while(b)
	{
		if(b & 1)	t = t * a % MOD;
		b >>= 1;
		a = a * a % MOD;
	}return t % MOD;
}
ll inv[MAXN];
void INV(int a,int p)//线性求到a的逆元
{
    inv[1] = 1;
    for (int i=2; i<=a; ++i)
        inv[i] = ((-(p/i) % p + p) % p)*inv[p%i]%p;
}

ll jc[MAXN],ni[MAXN];
ll C(ll m,ll n)
{return jc[m] * ni[n] % MOD * ni[m - n] % MOD;}
//ll C(ll m,ll n)
//{return jc[m] * ksm(jc[n],MOD - 2) % MOD * ni[m - n] % MOD;}
ll a[3];
int read()
{
    int x = 0,f = 1;
    char c = getchar();
    while(c > '9' || c < '0')    {if(c == '-')    f = -1;c = getchar();}
    while(c >= '0' && c <= '9')    {x = (x << 3) + (x << 1) + c - '0';c = getchar();}
    return x * f;
}
void solve()
{
	int n = read(),m = read();
//	int n,m;
//	scanf("%d %d",&n,&m);
	for(int i = 1;i <= n;++i){
		int x = read();
//		int x;
//		scanf("%d",&x);
		++a[x];
	}
	
	ll ans = 0;
	ll pw2 = 1;
	ll Min = min(a[2],m * 1ll);
//	for(int )
	for(int i = 0;i <= Min;++i)
	{
		if(a[1] >= m - i && a[2] >= i)
		{
			ans += pw2 * C(a[1],m - i) % MOD * C(a[2],i) % MOD;
        	ans %= MOD;
		}
        pw2 = pw2 * 2ll % MOD;
	}
	printf("%lld\n",ans);
}
int main()
{
//	freopen("out.txt","r",stdin);
	jc[0] = 1;ni[0] = 1;
	INV(MAXN - 1,MOD);
	for(int i = 1;i < MAXN;++i)	jc[i] = jc[i - 1] * i % MOD;
	for(int i = 1;i < MAXN;++i)	ni[i] = ni[i - 1] * inv[i] % MOD;
// 	for(int i = 1;i < MAXN;++i)	ni[i] = ksm(jc[i],MOD - 2);
	int t = 1;
//	scanf("%d",&t);
	while(t--)
	solve();
	return 0;
 } 

H

一个很像杭电寒假多校的中的一道题...
知识点:博弈SG函数
首先颜色只有两种,这启发我们用一个二进制串来保存这个序列。加入我们设0是黑色,1是白色
操作到最后的序列就是全0的,那么前一次的序列要么只有2个白色,要么就是只有第一个块时白色的...这些其实都不重要,我们只需要暴力去对当前的序列进行修改就行。
设dp[0] = 0,表示全0的局,牛妹直接输掉
其他的都是-1,对于每一个询问的答案,我们直接把它丢进dfs里面,然后对当前的状态x(就是给定的那个字符串)进行操作,要么是操作一,选择一个i > 1的白色方块,然后选择一个小于i的任意一个方块,使其变色,要么是操作二,如果第一个是白色,就把它变色
最后对所有的情况取一个最大值(因为双方都绝对聪明),由于dp数组值是-1,0,1,那么最好的情况不就是当前的状态可以带领我们走向胜利(dp[x] = 1)
输出就是dp[i] ? "Yes":"No";
AC代码:

点击查看代码
#include <iostream>
#include <cstring>
#include <vector> 
#include <algorithm>
#define lowbit(x) (x & (-x))
#include <cmath>
using namespace std;
typedef long long ll;
const int MOD = 998244353;
const int inf = 1e9 + 7;
const int MAXN = 2e5 + 7;
char s[MAXN];
int dp[2000];
int n;
int dfs(int x)
{
	if(x == 0)	return 0;
	if(dp[x] != -1)	return dp[x];
	
	if(x & 1)
	dp[x] = max(dp[x],dfs(x ^ 1) ^ 1);
	for(int i = 0;i < n;++i)
	{
		if(x >> i & 1)
		{
			for(int j = 0;j < i;++j)
			dp[x] = max(dp[x],dfs(x ^ (1 << i) ^ (1 << j)) ^ 1);
		}
	}
	return dp[x];
}
/*
1
10
bbwbbwbbwb
*/
void solve()
{
	scanf("%d",&n);
	scanf(" %s",s);
	int now = 0;
	for(int i = 0;i < n;++i)
	if(s[i] == 'w')
	now |= (1 << i);
	printf("%s\n",dfs(now) ? "Yes" : "No");
}
int main()
{
	memset(dp,-1,sizeof dp);
	int t = 1;
	scanf("%d",&t);
	while(t--)
	solve();
	return 0;
}

还剩下A,待补

Codeforces Globe Round 19

咕咕咕

posted @ 2022-02-18 15:26  K0njac  阅读(29)  评论(0编辑  收藏  举报