Preface
最下班的一集,100min 的时候手速过了六题,本来以为是完美开场,没想到是坐牢的开始
J 题很快推出了一个 \(O(n)\) 计算的组合式子,然后扔给徐神找生成函数做法,中间给出了几个要写快速阶乘算法的假做法后发现这题不太可做
祁神开始转战 D 题后给了个基于纳什均衡的很对的 DP 做法,然后二分的转移写了半天过不了样例,最后换成枚举后正确性是有保证了,但交上去又 TLE 了
过的人比较多的 G 题因为基本没怎么话时间想因此没意识到是个丁真题,最后徐神发现了 \(\sqrt {1000}\) 以内的质数只有 \(11\) 个,但已经于事无补了
最后靠着前期优秀的罚时苟了个尚可的排名,但后 200min 不出题让人感觉梦回去年啊
Floor Tiles
徐神看了眼就秒了,我题意都不知道,纯纯的被带飞
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) {
int n, m, k; std::cin >> n >> m >> k;
int x, y; std::string t; std::cin >> x >> y >> t;
x--; y--;
if(k < n + m) {
std::cout << "No\n";
continue;
}
std::vector<std::string> ans(n, std::string(m, 'A'));
if((x + y & 1) ^ (t[0] == 'A'))
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
ans[i][j] = char('A' + (i + j & 1));
else
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
ans[i][j] = char('B' - (i + j & 1));
auto is_circle = [&](int i, int j) -> bool {
return ans[i][j] == 'A' && ans[i][j + 1] == 'B' &&
ans[i + 1][j] == 'B' && ans[i + 1][j + 1] == 'A';
};
int cir = 0, type = 0;
for(int i = 0; i < n - 1; ++i) for(int j = 0; j < m - 1; ++j) if(is_circle(i, j)) {
cir += 1;
if(i == x && j == y) type = 1;
}
if(n + m + cir < k) {
std::cout << "No\n";
continue;
}
for(int i = 0; i < n - 1 && n + m + cir > k; ++i) for(int j = 0; j < m - 1 && n + m + cir > k; ++j) {
if(is_circle(i, j)) {
if(type) ans[i][j + 1] = 'A';
else ans[i][j] = 'B';
cir -= 1;
}
}
std::cout << "Yes" << char(10);
for(int i = 0; i < n; ++i) std::cout << ans[i] << char(10);
}
return 0;
}
MST
感觉数据水了赛时随便写了个带 \(\log\) 的东西交上去一发过了,后面发现是有严格根号的做法的说
这种询问集合的题很容易想到根号分治,显然当 \(k_i\) 较大时直接 \(O(m\cdot\alpha(m))\) 跑一个克鲁斯卡尔即可
当 \(k_i\) 较小时理论上我们需要严格的 \(O(k_i^2)\) 做法,很容易想到用 Prim,但问题在于我们无法快速找出这些点之间的边
一种解决方案是对边的存储也使用根号分治,将点按照度数分类
度数 \(>\sqrt m\) 的点数较少,可以用临界矩阵存储;否则剩下的度数 \(\le \sqrt m\) 的点对应的边数较少,可以用邻接表存储
当然比赛时我直接用 map
存两点之间的边,然后还是暴力跑克鲁斯卡尔,交上去也过了
#include<cstdio>
#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
#define RI int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=100005,LIM=250;
struct edge
{
int x,y,v;
inline edge(CI X=0,CI Y=0,CI V=0)
{
x=X; y=Y; v=V;
}
friend inline bool operator < (const edge& A,const edge& B)
{
return A.v<B.v;
}
}E[N]; int n,m,q,k,p[N],fa[N]; map <pi,int> rst;
inline int getfa(CI x)
{
return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
namespace Case1
{
inline void solve(void)
{
RI i,j; for (i=1;i<=k;++i) fa[p[i]]=p[i];
vector <edge> edges;
for (i=1;i<=k;++i) for (j=i+1;j<=k;++j)
{
int x=p[i],y=p[j]; if (x>y) swap(x,y);
if (rst.count(pi(x,y))) edges.emplace_back(x,y,rst[pi(x,y)]);
}
sort(edges.begin(),edges.end());
int cnt=0; long long ret=0;
for (auto [x,y,v]:edges)
{
if (getfa(x)!=getfa(y))
{
fa[getfa(x)]=getfa(y);
++cnt; ret+=v;
}
if (cnt==k-1) break;
}
if (cnt!=k-1) puts("-1"); else printf("%lld\n",ret);
}
};
namespace Case2
{
bool key[N];
inline void solve(void)
{
RI i; for (i=1;i<=k;++i) fa[p[i]]=p[i],key[p[i]]=1;
int cnt=0; long long ret=0;
for (i=1;i<=m;++i)
{
int x=E[i].x,y=E[i].y;
if (!key[x]||!key[y]) continue;
if (getfa(x)!=getfa(y))
{
fa[getfa(x)]=getfa(y);
++cnt; ret+=E[i].v;
}
if (cnt==k-1) break;
}
for (i=1;i<=k;++i) key[p[i]]=0;
if (cnt!=k-1) puts("-1"); else printf("%lld\n",ret);
}
}
int main()
{
RI i; for (scanf("%d%d%d",&n,&m,&q),i=1;i<=m;++i)
{
scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].v);
if (E[i].x>E[i].y) swap(E[i].x,E[i].y);
}
for (sort(E+1,E+m+1),i=1;i<=m;++i)
if (!rst.count(pi(E[i].x,E[i].y))) rst[pi(E[i].x,E[i].y)]=E[i].v;
while (q--)
{
for (scanf("%d",&k),i=1;i<=k;++i) scanf("%d",&p[i]);
if (k<=LIM) Case1::solve(); else Case2::solve();
}
return 0;
}
Red Walking on Grid
首先不难发现假设起点在一个连通块的最左边,则存在一种最优的方案使得全程不会往左走
那么在第一行的点只有向下和向右两种操作了;在第二行的点就只能向上或向下
考虑 DP,令 \(f_{i,j,0/1}\) 表示当前位于 \((i,j)\) 位置,与其同列的另一个格子是否被经过的最长长度,转移十分显然
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int n,f[2][N][2]; char a[2][N];
int main()
{
RI i,j; for (scanf("%d",&n),i=0;i<2;++i) scanf("%s",a[i]+1);
for (j=1;j<=n;++j)
{
for (i=0;i<2;++i) if (a[i][j]=='R') f[i][j][0]=max(f[i][j][0],1);
for (i=0;i<2;++i) if (a[i][j]=='R')
{
if (a[i^1][j]=='R') f[i^1][j][1]=max(f[i^1][j][1],f[i][j][0]+1);
}
for (i=0;i<2;++i) if (a[i][j]=='R')
{
if (j+1<=n&&a[i][j+1]=='R') f[i][j+1][0]=max(f[i][j+1][0],max(f[i][j][0],f[i][j][1])+1);
}
}
int ans=0; for (i=0;i<2;++i) for (j=1;j<=n;++j) ans=max(ans,max(f[i][j][0],f[i][j][1])-1);
return printf("%d",ans),0;
}
Taking Candies
标算感觉十分奇怪,这里讲下祁神赛时写的做法,正确性是有保证的,但赛时 \(O(n\times x^2)\) TLE 了
首先很容易发现两人每次拿糖果都是拿当前最多的一袋,而关注最后一轮博弈过程,显然是谁钱多谁就拿走最后一袋
考虑从后往前递推,不难发现这是个纳什博弈模型,令 \(f_{i,x}\) 表示还剩下 \(i\) 袋糖果,先手此时手中有 \(x\) 块钱,在剩下的过程中最多能得到的糖果数
考虑枚举先手这一轮使用的筹码数 \(M\),若后手选择阻止先手(需要至少 \(M+1\) 的钱)会导致后续给先手带来的收益更大,则后手显然不会操作,则有转移:
\[f_{i,x}\leftarrow f_{i-1,x-M}+a_i\ (f_{i-1,x-M}+a_i<f_{i-1,x+M+1}) \]否则分情况讨论一些特殊情况即可,总复杂度 \(O(n\times x^2)\),赛时最后挣扎了一波搞了个记搜剪了点状态,发现还是过不了
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+5;
int n, xx, yy, tot, A[N];
int f[N][205];
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> xx >> yy;
tot = xx+yy;
for (int i=1; i<=n; ++i) cin >> A[i];
sort(A+1, A+n+1);
for (int x=(tot+1)/2; x<=tot; ++x) f[1][x]=A[1];
for (int i=2; i<=n; ++i){
for (int x=0; x<=tot; ++x){
int L=0, R=min(x, tot-x);
for (int M=L; M<=R; ++M){
if (f[i-1][x-M]+A[i] < f[i-1][x+M+1]){
f[i][x] = max(f[i][x], f[i-1][x-M]+A[i]);
}else{
if (M >= tot-x) f[i][x] = max(f[i][x], f[i-1][x-M]+A[i]);
else f[i][x] = max(f[i][x], f[i-1][x+M+1]);
}
}
}
}
cout << f[n][xx] << '\n';
return 0;
}
GCD VS XOR
徐神开场写的,还抢到了二血,而我题意都不知道
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) {
int64_t x; std::cin >> x;
int64_t l = x&-x;
if(l == x) std::cout << "-1\n";
else std::cout << (x ^ l) << char(10);
}
return 0;
}
Mixed Juice
这场的防 AK,直接弃疗
The Set of Squares
唐完了,早知道就不该把 J 题扔给徐神,不然徐神早一小时看这题可能都出了
需要发现 \(<\sqrt {1000}=32\) 的质数只有 \(11\) 个,这也就意味着每个数最多含有一个 \(\ge 32\) 的质数
因此这些较大的质数只能相互匹配,所以可以分开来处理
而较小的质数由于数量很小,并且我们只关心每个质数此时的次数是奇数还是偶数,因此可以状压
用一个类似背包的 DP 即可做到 \(O(2^{11}\times n)\),转移十分显然
#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=1<<11,pri[11]={2,3,5,7,11,13,17,19,23,29,31},mod=1e9+7;
int n,x,f[2][N][2]; vector <pi> vec[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
int main()
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
{
scanf("%d",&x); int mask=0,val=1;
for (j=0;j<11;++j)
{
while (x%pri[j]==0)
{
if ((mask>>j)&1) val=1LL*val*pri[j]%mod;
x/=pri[j]; mask^=(1<<j);
}
}
vec[x].push_back(pi(mask,val));
}
int now=0; f[0][0][0]=1;
auto calc=[&](CI x,CI y)
{
int val=1; for (RI i=0;i<11;++i)
if (((x&y)>>i)&1) val=1LL*val*pri[i]%mod;
return val;
};
for (auto [mask,val]:vec[1])
{
int nxt=now^1;
for (i=0;i<N;++i) f[nxt][i][0]=f[now][i][0];
for (i=0;i<N;++i) inc(f[nxt][i^mask][0],1LL*f[now][i][0]*val%mod*calc(mask,i)%mod);
now=nxt;
}
for (j=32;j<=1000;++j) if (!vec[j].empty())
{
for (i=0;i<N;++i) f[now][i][1]=0;
for (auto [mask,val]:vec[j])
{
int nxt=now^1;
for (i=0;i<N;++i) f[nxt][i][0]=f[now][i][0],f[nxt][i][1]=f[now][i][1];
for (i=0;i<N;++i)
{
inc(f[nxt][i^mask][0],1LL*f[now][i][1]*val%mod*calc(mask,i)%mod*j%mod);
inc(f[nxt][i^mask][1],1LL*f[now][i][0]*val%mod*calc(mask,i)%mod);
}
now=nxt;
}
}
return printf("%d",(f[now][0][0]-1+mod)%mod),0;
}
Instructions Substring
不难发现枚举左端点 \(l\),假设其对应的第一个走到 \((x,y)\) 的位置为 \(r\),则贡献为 \(n-r+1\)
判断是否走到的充要条件是 \(X_r-X_{l-1}=x\and Y_r-Y_{l-1}=y\),其中 \(X,Y\) 分别为两维坐标的前缀和数组
倒着用 map
维护一下最靠左的 \((X_i,Y_i)\) 即可
#include<cstdio>
#include<iostream>
#include<map>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int n,x,y,px[N],py[N]; map <pi,int> fst; char a[N];
int main()
{
RI i; scanf("%d%d%d%s",&n,&x,&y,a+1);
if (x==0&&y==0) return printf("%lld",1LL*n*(n+1)/2LL),0;
for (i=1;i<=n;++i)
{
px[i]=px[i-1]; py[i]=py[i-1];
if (a[i]=='A') --px[i]; else
if (a[i]=='D') ++px[i]; else
if (a[i]=='W') ++py[i]; else
if (a[i]=='S') --py[i];
}
//for (i=1;i<=n;++i) printf("%d %d\n",px[i],py[i]);
long long ans=0; for (i=n;i>=1;--i)
{
fst[pi(px[i],py[i])]=i;
pi tmp=pi(x+px[i-1],y+py[i-1]);
if (fst.count(tmp)) ans+=n-fst[tmp]+1;
}
return printf("%lld",ans),0;
}
Red Playing Cards
看完题很容易想到一个暴力的 \(O(n^3)\) 区间 DP 做法,但题面显然要求的是 \(O(n^2)\)
考虑对于一个区间,在删除它之前可以先对其内部的一些较小的区间进行操作;而不在其内部的区间要么和它没有影响,要么会导致该区间无法操作
因此把所有区间按照长度从小到大处理,每次枚举其内部从哪些区间进行了操作,总复杂度 \(O(n^2)\)
#include<bits/stdc++.h>
using namespace std;
const int N = 6005;
int n;
int A[N], pos[N][2], len[N], id[N];
int f[N][N];
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
pos[0][0]=0, pos[0][1]=2*n+1, len[0]=2*n+1;
for (int i=1; i<=2*n; ++i){
cin >> A[i];
if (pos[A[i]][0]==0) pos[A[i]][0]=i;
else pos[A[i]][1]=i, len[A[i]]=pos[A[i]][1]-pos[A[i]][0];
}
for (int i=0; i<=n; ++i) id[i]=i;
sort(id, id+n+1, [&](int a, int b){return len[a]<len[b];});
for (int dd=0; dd<=n; ++dd){
int x=id[dd];
int L=pos[x][0], R=pos[x][1];
f[x][L]=x;
for (int i=L+1; i<=R; ++i){
f[x][i] = f[x][i-1]+x;
if (pos[A[i]][1]==i && pos[A[i]][0]>=L){
f[x][i] = max(f[x][i], f[x][pos[A[i]][0]-1] + f[A[i]][pos[A[i]][1]]);
}
}
// printf("dd=%d f[%d]=%d\n", dd, x, f[x][R]);
}
// for (int i=0; i<=n*2+1; ++i) printf("%d ", f[0][i]); puts("");
cout << f[0][2*n+1] << '\n';
return 0;
}
Involutions
只会这题的组合意义式子,但 \(O(n)\) 的做法还是过不了,生成函数也没办法处理
考虑枚举不动点的个数 \(i\),则剩下的 \(2y=n-i\) 个点构成的置换环长度都必须恰好为 \(2\)
\(C_{2y}^y\) 表示选出 \(y\) 个点作为起点,\(y!\) 表示剩下的 \(y\) 个点关于这些点的匹配方案,但由于两边是对称的,因此要除以 \(2^y\)
最后的式子就是:
\[\sum_{y=0}^{2y\le n} \frac{a^{n-2y}\times C_{2y}^y\times y!}{2^y} \]然而因为没推递推的关系式,所以根本想不到正解是整式递推(知道了也没用反正写不来),直接弃疗
Postscript
白天打牛客多校,晚上还有 CF 1+2,明天还有杭电多校,真是冲冲又实实啊
标签:std,const,int,多校,pos,2024,牛客,ans,include From: https://www.cnblogs.com/cjjsb/p/18310413