cunzai_zsy0531

关注我

2021国庆校队集训

模拟赛

Day 1-qyc

T1 ARC071E

这三种操作看起来比较难处理,因为它们不仅修改了字符,还修改了长度。注意到一个字符串通过若干次这三种操作可以得到自己,所以考虑设一个值(\(A=1,B=2\)),使得所有的操作对整个字符串值的和 \(\bmod\ 3\) 都不变。所以最后直接按照这个值判一下就行了。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
const int N=1e5+13;
char s[N],t[N];
int n,m,q,sa[N],sb[N];
int main(){
	scanf("%s%s",s+1,t+1);n=strlen(s+1),m=strlen(t+1);
	for(int i=1;i<=n;++i) sa[i]=(sa[i-1]+1+(s[i]=='B'))%3;
	for(int i=1;i<=m;++i) sb[i]=(sb[i-1]+1+(t[i]=='B'))%3;
	scanf("%d",&q);
	while(q--){
		int la,ra,lb,rb;scanf("%d%d%d%d",&la,&ra,&lb,&rb);
		puts((sa[ra]-sa[la-1]+3)%3==(sb[rb]-sb[lb-1]+3)%3?"YES":"NO");
	}
	return 0;
}

T2 ARC066E

枚举两个相邻的减号,在它们之后的所有符号都可以变成正的,他们之间的都是负的,它们前面的都不管。然后就这些情况取个最大值就行了。

点击查看代码
#include<iostream>
#include<cstdio>
typedef long long ll;
inline ll max(const ll &a,const ll &b){return a>b?a:b;}
inline ll min(const ll &a,const ll &b){return a<b?a:b;}
inline int Abs(const int &x){return x<0?-x:x;}
const int N=1e5+13;
int n,a[N],b[N],m;
ll res;
char op[N];
int main(){
	scanf("%d%lld",&n,&res);--n;
	for(int i=1;i<=n;++i){
		std::cin>>op[i];if(op[i]=='-') b[++m]=i;
		scanf("%d",&a[i]);
		res+=Abs(a[i]);
	}
	ll sum=0,now=0;
	for(int i=1;i<m;++i){
		ll tmp=now;
		for(int j=b[i];j<b[i+1];++j) tmp+=a[j];
		if(!sum) sum=tmp;
		else sum=min(sum,tmp);
		now+=a[b[i]];
	}
	printf("%lld\n",res-sum*2);
	return 0;
}

T3 ARC097F

首先注意到,如果一个点的度为 \(1\) 且为黑色,那么可以直接把它删掉。这样剩下的树叶子就都是白色了。设剩下的树有 \(n\) 个点。

考虑如果猫必须回到它进入的地方的话,那么花费是 \(2(n-1)+\sum_u [(deg_u+[col_u=='B'])\bmod 2]\)

现在我们想让总花费最小,那么相当于要从树上选择一条花费最大的路径,然后用上面的总花费减去它。找花费最大的路径可以使用类似求树的直径的做法,把每个点都当作 \(lca\) 求一次,求的时候维护儿子里的最大值和次大值即可。

这题需要注意一些边界条件和小细节。

点击查看代码
#include<iostream>
#include<cstdio>
#include<queue>
inline int max(const int &a,const int &b){return a>b?a:b;}
const int N=1e5+13;
struct Edge{int v,nxt;}e[N<<1];
int n,h[N],tot,deg[N],f[N],res=-1,cntedge;
char in[N];
bool state[N],vis[N];
inline void add_edge(int u,int v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
inline void bfs(){
	std::queue<int> q;while(!q.empty())q.pop();
	for(int i=1;i<=n;++i)
		if(deg[i]==1&&state[i]) q.push(i),vis[i]=1;
	while(!q.empty()){
		int u=q.front();q.pop();--cntedge;
		for(int i=h[u];i;i=e[i].nxt){
			int v=e[i].v;if(vis[v]) continue;
			state[v]^=1;deg[v]--;
			if(deg[v]==1&&state[v]) q.push(v),vis[v]=1;
		}
	}
}
void dfs(int u,int fa){
	int mx1=0,mx2=0;
	for(int i=h[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa||vis[v]) continue;
		dfs(v,u);
		f[u]=max(f[u],f[v]+1);
		if(f[v]+1>mx1) mx2=mx1,mx1=f[v]+1;
		else if(f[v]+1>mx2) mx2=f[v]+1;
	}
	f[u]+=(state[u]?1:-1);
	res=max(res,mx1+mx2+(state[u]?1:-1));
}
int main(){
	scanf("%d",&n);cntedge=n-1;
	for(int i=1;i<n;++i){
		int u,v;scanf("%d%d",&u,&v);
		deg[u]++,deg[v]++;
		add_edge(u,v),add_edge(v,u); 
	}
	scanf("%s",in+1);
	if(n==1) return puts(in[1]=='B'?"0":"1"),0;
	for(int i=1;i<=n;++i) state[i]=((deg[i]&1)^(in[i]=='W'));
	bfs();
	int ans=0;
	for(int i=1;i<=n;++i)
		if(!vis[i]) ans+=state[i];
	int rt=1;
	while(rt<=n&&vis[rt]) ++rt;
	if(rt>n) return puts("0"),0;
	dfs(rt,0);
	if(!cntedge) puts(in[rt]=='B'?"0":"1");
	else printf("%d\n",2*cntedge+ans-res-1);
	return 0;
}

T4 ARC096F

把这棵树拆成一些子树作为物品,每个物品都是以某个点为根的子树,花费 \(\sum m_i\),价值为 \(siz\)。除了原树以外其他物品都最多选 \(D\) 个。

显然由于 \(D\) 很大所以无法直接做多重背包。考虑一个错误的贪心,当 \(\frac{w_i}{v_i}<\frac{w_j}{v_j}\) 的时候选 \(i\),这是错误的因为 \(w_i\)\(w_j\) 不一定相等。当它们相等的时候这是没问题的,也就是说,\(i\) 如果取了 \(w_i\) 个,那么 \(w_j\leq w_i\)

所以可以发现,背包只需要处理最多 \(\min(n,D)\) 个物品(因为 \(siz\leq n\)),剩下的直接贪心即可。用二进制分组做多重背包复杂度 \(O(n^3\log n)\)

正解代码待补。

Day 2-ljc

T1 P4050(有修改)

枚举听哪张牌,判断各个和牌方式是否能和牌。第一种和牌方式可以贪心,每种花色从小到大处理,先处理刻子再处理顺子即可。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
std::map<char,int> ms;
char name[8]={' ','E','W','S','N','Z','F','B'};
inline void init(){ms['E']=1,ms['W']=2,ms['S']=3,ms['N']=4,ms['Z']=5,ms['F']=6,ms['B']=7;}
const int N=15;
int type;
char in[5][N];
int l[5],up[5],a[5][N],b[5][N],ans[5][N],cnt[5];
inline void print(){
	for(int i=1;i<=4;++i){
		if(!cnt[i]) printf("0\n");
		else{
			if(i!=4){
				for(int j=1;j<=cnt[i];++j) printf("%d",ans[i][j]);
				puts("");
			}
			else{
				for(int j=1;j<=cnt[i];++j) printf("%c",name[ans[i][j]]);
				puts("");
			}
		}
	}
}
inline bool check0(){
	for(int j=1;j<=4;++j)
		for(int i=1;i<=up[j];++i) b[j][i]=a[j][i];
	int cnt=0;
	while(cnt<4){
		for(int j=1;j<=4;++j){
			int i=1;
			while(i<=up[j]&&!b[j][i]) ++i;
			if(i>up[j]) continue;
			if(b[j][i]>=3) b[j][i]-=3;
			else{
				if(j!=4&&i<=up[j]-2&&b[j][i+1]&&b[j][i+2]) --b[j][i],--b[j][i+1],--b[j][i+2];
				else return 0;
			}
		}
		++cnt;
	}
	return 1;
}
inline bool check1(){
	for(int j=1;j<=4;++j){
		for(int i=1;i<=up[j];++i){
			if(a[j][i]<2) continue;
			a[j][i]-=2;bool flag=0;
			if(check0()) flag=1;
			a[j][i]+=2;
			if(flag) return 1;
		}
	}
	return 0;
}
inline bool check2(){
	for(int j=1;j<=4;++j)
		for(int i=1;i<=up[j];++i)
			if(a[j][i]>2||a[j][i]==1) return 0;
	return 1;
}
inline bool check3(){
	int cntt=0;
	for(int j=1;j<=3;++j){
		if(a[j][1]>2||!a[j][1]||a[j][9]>2||!a[j][9]) return 0;
		cntt+=(a[j][1]==2)+(a[j][9]==2);
	}
	for(int i=1;i<=7;++i){
		if(a[4][i]>2||!a[4][i]) return 0;
		cntt+=(a[4][i]==2);
	}
	return cntt==1;
}
inline bool check(int k,int x){
	if(a[k][x]==4) return 0;
	a[k][x]++;
	bool flag=0;
	if(check1()) flag=1;
	else if(check2()) flag=1;
	else if(check3()) flag=1;
	--a[k][x];
	return flag;
}
int main(){
	init();
	scanf("%d",&type);
	scanf("%s%s%s%s",in[1]+1,in[2]+1,in[3]+1,in[4]+1);
	for(int i=1;i<=4;++i) l[i]=strlen(in[i]+1),up[i]=9-(i==4)*2;
	for(int j=1;j<=3;++j)
		for(int i=1;i<=l[j];++i)
			if(in[j][i]!='0') a[j][in[j][i]-'0']++;
	for(int i=1;i<=l[4];++i)
		if(in[4][i]!='0') a[4][ms[in[4][i]]]++;
	check(4,5);
	for(int j=1;j<=4;++j)
		for(int i=1;i<=up[j];++i)
			if(check(j,i)) ans[j][++cnt[j]]=i;
	print();
	return 0;
}
//a-条 b-饼 c-万 d-风
//EWSNZFB 

T2 P1463 加强版

一个数 \(x=\sum_{p_i\in prime}p_i^{k_i}\) 的约数个数为 \(\prod (p_i+1)\)。原题 \(n\leq 2\times 10^9\),注意到大的质数的指数一定小于等于小质数的指数,直接爆搜每个质数的指数即可。

加强版 \(n\leq 10^{200}\),考虑打表,根据一些奇怪的性质找到这些反素数发现只有几千个,于是就做完了……

这题太毒瘤了……正解代码待补。

T3 CF232C

注意到 \(f\) 其实就是斐波那契数列,每一层点都由左边的 \(f_{i-1}\) 个点和右边的 \(f_{i-2}\) 个点拼起来,中间有几条边相连。这样的图肯定是考虑一层一层递归求解,如果两个点在这一层不同在左边或同在右边,那么枚举几条路径求一个最短的就行了。

细节太多,赛时得分 50pts。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
typedef long long ll;
inline ll min(const ll &a,const ll &b){return a<b?a:b;}
inline ll rd(){
	ll res=0;char c=getchar();
	for(;!isdigit(c);c=getchar());
	for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
	return res;
}
const int N=100+13;
int n,maxn;
ll f[N],d[N][4][4],dis[N][4];
inline void init(){
	f[0]=1,f[1]=2;
	for(maxn=2;f[maxn-1]<=1e16;++maxn) f[maxn]=f[maxn-1]+f[maxn-2];
}
inline void work(ll a,ll b){
	for(int i=n;i>=0;--i){
		dis[i][0]=1,dis[i][1]=a,dis[i][2]=b,dis[i][3]=f[i];
		if(i&&a>f[i-1]) a-=f[i-1];
		if(i&&b>f[i-1]) b-=f[i-1];
	}
}
ll solve(int n,int a,int b){
	if(dis[n][a]==dis[n][b]) return 0;
	if(n<=2) return 1;
	if(d[n][a][b]!=-1) return d[n][a][b];
	if(dis[n][a]>f[n-1]) return d[n][a][b]=solve(n-2,a,b);
	if(dis[n][b]<=f[n-1]) return d[n][a][b]=min(solve(n-1,a,b),min(solve(n-1,0,a),solve(n-1,a,3))+min(solve(n-1,0,b),solve(n-1,b,3))+2);
	return d[n][a][b]=min(solve(n-1,0,a),solve(n-1,a,3))+solve(n-2,0,b)+1;
}
int main(){
	init();
	int T=rd();n=rd();
	if(n>maxn) n=maxn;
	while(T--){
		memset(d,-1,sizeof d);
		ll a=rd(),b=rd();
		if(a>b) a^=b^=a^=b;
		work(a,b);
		printf("%lld\n",solve(n,1,2));
	}
	return 0;
}

T4 CF1290D

如果对于每两个点都询问一遍是否有贡献,那么需要 \(2n^2\) 次询问。

在这个基础上考虑分块,每 \(\frac{k}{2}\) 个点分一个块,这样只需要查询块间的贡献,块内的在询问中就已经知道了。但是这样做次数是 \(\frac{2n^2}{k}\) 的。

继续考虑优化查询块贡献的过程,如果首先枚举一个长度 \(k\),然后对于所有间隔为 \(k\) 的块查询,那么根据一番证明次数上限是 \(\frac{3n^2}{2k}\)

如果这个过程弄成一个“之”字形,也就是查 \(x\rightarrow x+1\rightarrow x-2\rightarrow x+3\ldots\) 一直查到顶了一边的端点,那么可以证明次数是 \(\frac{n^2}{k}\) 的。

题目太毒瘤,正解代码待补。

Day 3-zsy

T1 P3478

换根dp板子题。

T2 2021山东省队二轮集训Day2T1

见《二轮集训Day2》。

T3 P3647

见《杂题选糊》。

T4 2021山东省队二轮集训Day2T2

见《二轮集训Day2》。

Day 4-zrz

T1 P7419

有非常简单的递归做法。

我的做法大概说的是,考虑设 \(f_i\) 表示有 \(i\) 层点的时候,每个无序对 \(lca\) 的和。然后就能推出一个式子:

\[f_i=4f_{i-1}+\sum_{j=2^{i-1}}^{2^i-1}j \]

最后要求的是有序对 \(lca\) 的和,那么可以用无序对减去所有点的编号之后乘二再加上所有点的编号即可。

点击查看代码
#include<iostream>
#include<cstdio>
inline int rd(){
	int res=0;char c=getchar();
	for(;!isdigit(c);c=getchar());
	for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
	return res;
}
void wt(int x){if(x>9)wt(x/10);putchar(x%10+'0');}
typedef long long ll;
const int N=1e6+13,mod=998244353;
inline int qpow(int a,int k){int s=1;for(;k;k>>=1,a=(ll)a*a%mod)if(k&1)s=(ll)s*a%mod;return s;}
const int inv2=qpow(2,mod-2);
int two[N],f[N],g[N],ans[N];
inline void init(){
	int n=1e6;
	two[0]=1;
	for(int i=1;i<=n;++i) two[i]=two[i-1]*2%mod;
	for(int i=1;i<=n;++i) f[i]=((ll)f[i-1]*4%mod+((ll)two[i-1]+two[i]-1+mod)*two[i-1]%mod*inv2%mod)%mod;
	for(int i=1;i<=n;++i) g[i]=(ll)two[i]*(two[i]-1)%mod*inv2%mod;
	for(int i=1;i<=n;++i) ans[i]=((f[i]-g[i]+mod)%mod*2%mod+g[i])%mod;
}
int main(){
	init();
	int T=rd();
	while(T--){int n=rd();wt(ans[n]),putchar('\n');}
	return 0;
}

T2 P7264

正解做法大概说的是你一直把两个点同时平移到左上角,最后你不能平移的时候,它们一定会经过边上的某一段然后相见,好像直接统计就行了。

我的做法比较阴间,说的是首先可以发现这个图是从一个格子开始,每一步都把当前的图向下向右平移组成一个由三个原图组成的新图这样下去。每两个格子之间都有唯一的最短路。根据上面那个生成过程,可以将一个点的坐标弄成一个三进制 \(60\) 位数。

接下来考虑使用类似整体二分的思想处理所有询问点,大概就是把所有点都放在同一个数组里,然后在函数参数里传入 \(l,r\) 表示当前路径只需要处理 \((l,r)\) 范围内的所有询问点。然后如果当前这一层两个点是 \(1\)\(2\),那一定会走 \(0\) 的边上,把这些点处理掉就行了。

考虑如果没有询问点了,就不需要再递归下去了,直接预处理出起点和终点分别到每一层的三个角的距离,递推一下就行,然后直接返回即可。这样时间复杂度和空间复杂度都是 \(O(n\log v)\),我常数太大所以空间有点爆,还有就是这样做细节太多所以最后赛时只拿了44pts,唉……

正解代码待补。

T3 P2260

上式子:

\[\begin{aligned} &\ \ \ \ \ \ \sum_{i=1}^n\sum_{j=1}^n[i\neq j](n\bmod i)(m\bmod j)\\ &=\big(\sum_{i=1}^n\sum_{j=1}^n (n-\lfloor\frac{n}{i}\rfloor\cdot i)(m-\lfloor\frac{m}{j}\rfloor\cdot j)\big)-\big(\sum_{i=1}^n (n-\lfloor\frac{n}{i}\rfloor\cdot i)(m-\lfloor\frac{m}{i}\rfloor\cdot i)\big)\\ &=(\sum_{i=1}^n\sum_{j=1}^n nm-mi\lfloor\frac{n}{i}\rfloor-nj\lfloor\frac{m}{j}\rfloor+ij\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor)-(\sum_{i=1}^n nm-mi\lfloor\frac{n}{i}\rfloor-ni\lfloor\frac{m}{i}\rfloor+i^2\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{i}\rfloor) \end{aligned}\]

对于上述八项分别整除分块即可。

注意 \(n,m\) 不同,如果上限是 \(n\),分块的是 \(\lfloor\frac{m}{i}\rfloor\),那么每次对 \(l\)\(r\) 的时候需要与 \(n\)\(\min\),即

\[r=\min(\lfloor\frac{m}{\lfloor\frac{m}{l}\rfloor}\rfloor,n) \]

还有:开O2优化之后,scanf%d 读入 long long 类型会 WA

点击查看代码
#include<iostream>
#include<cstdio>
#define int long long
inline int min(const int &a,const int &b){return a<b?a:b;}
typedef long long ll;
const int mod=19940417;
inline int calc1(int n){
	int res=0;
	for(int l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		res+=(ll)(l+r)*(r-l+1)/2%mod*(n/l)%mod,res%=mod;
	}
	return res;
}
inline int calc3(int n,int m){
	int res=0;
	for(int l=1,r;l<=n;l=r+1){
		r=min(m/(m/l),n);
		res+=(ll)(l+r)*(r-l+1)/2%mod*(m/l)%mod,res%=mod;
	}
	return res;
}
inline int mul2(__int128 n){return n*(n+1)*(2*n+1)/6%mod;}
inline int calc4(int n,int m){
	int res=0;
	for(int l=1,r;l<=n;l=r+1){
		r=min(min(n/(n/l),m/(m/l)),n);
		res+=(mul2(r)-mul2(l-1)+mod)%mod*(n/l)%mod*(m/l)%mod,res%=mod;
	}
	return res;
}
signed main(){
	int n,m;
	scanf("%lld%lld",&n,&m);if(n>m) n^=m^=n^=m;
	int ans1=(ll)n*m%mod*n%mod*m%mod,ans2=(ll)calc1(n)*m%mod*m%mod,ans3=(ll)calc1(m)*n%mod*n%mod,ans4=(ll)calc1(n)*calc1(m)%mod;
	int Ans1=(((ans1-ans2+mod)%mod-ans3+mod)%mod+ans4)%mod;
	int ans5=(ll)n*m%mod*n%mod,ans6=(ll)calc3(n,m)*n%mod,ans7=(ll)calc1(n)*m%mod,ans8=calc4(n,m);
	int Ans2=(((ans5-ans6+mod)%mod-ans7+mod)%mod+ans8)%mod;
	printf("%lld\n",(Ans1-Ans2+mod)%mod);
	return 0;
}

T4 P4093

设每个位置初始值为 \(a_i\),在所有时刻能够达到的最大值和最小值分别为 \(mx_i\)\(mn_i\),那么dp方程为:

\[f_i=\max_{mx_j\leq a_i,a_j\leq mn_j,j< i}f_j+1 \]

三位偏序经典问题,使用cdq分治优化dp即可 \(O(n\log n)\)

正解代码待补。

posted @ 2022-04-22 17:00  cunzai_zsy0531  阅读(52)  评论(0编辑  收藏  举报