周总结 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
咕咕咕