国庆模拟赛一总结

看到ATZdhjeb在搞我也来整一下,毕竟模拟赛怎么能不总结呢?

T1

题面

首先直接按题模拟能有50pts,然后我就不会了。

赛后别人都写的是差分,考的时候想了但没写/ll

90pts 按行差分

其实能直接拿100pts的,也许是数据水了。

之前不会的技巧:要想给某一个区间加上一个数,珂以在差分数组的这个区间的开头加上 s,在末尾减去 s

这样子维护每一行,时间复杂度减少至 O(qn)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2*114514,M=1919810;
ll n,q,a[2005][2005];
int main(){
	//freopen("xor.in","r",stdin);
	//freopen("xor.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=q;++i){
		ll r,c,l,s;
		cin>>r>>c>>l>>s;
		for(int i=1;i<=l;++i){
			a[r+i-1][c]+=s;
			if(c+i<=n) a[r+i-1][c+i]-=s;
		}
	}
	ll ans=0;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			a[i][j]+=a[i][j-1],ans^=a[i][j];
	cout<<ans;
	return 0;
}

正解 二维差分

这个就要高级一些了,直接把修改的复杂度降到了一次 O(1),还要融合一些容斥的思想。

矩阵前缀和:sumi,j=sumi1,j+sumi,j1sumi1,j1+ai,j

这里的三角形前缀和:sumi,j=sumi+1,j+sumi+1,j+1sumi+2,j+1

如图所示:1696163826616.png容斥原理,总=蓝+绿-红

在统计答案的时候答案就是矩形的前缀和+三角形前缀和。

题解code

#include<cstdio>
#define maxn 3039
using namespace std;
int n, m, r, c, l, s;
long long ans,f[maxn][maxn], g[maxn][maxn];
void add1(int x, int y, int s){if(x<=n&&y<=n)f[x][y] += s;} //三角形
void add2(int x, int y, int s){if(x<=n&&y<=n)g[x][y] += s;} //矩形
int main(){
    scanf("%d%d", &n, &m);
    while(m--){
        scanf("%d%d%d%d", &r, &c, &l, &s);
        add1(r, c, s); //顶点加s
        add1(r+l, c+l, -s);//右下方减s
        add2(r+l, c, -s);//维护需要减去的矩阵,注意是减去
        add2(r+l, c+l, s);
    }
    for(int i = 1; i < n+1; i++)
        for(int j = 1; j < n+1; j++){
            if(i>1)f[i][j] -= f[i-2][j-1];
            f[i][j] += f[i-1][j-1] + f[i-1][j]; //三角形的前缀和
            g[i][j] += g[i-1][j] + g[i][j-1] - g[i-1][j-1];//矩阵的前缀和
            ans ^= f[i][j]+g[i][j];
    }
    printf("%lld\n", ans);
    return 0;
}

T2

link

诈骗题,但是当你结论之后发现其实非常好推,就是要是一开始方向错了后面就全错了。

首先不能贪心,也没有dp,更没有SG函数,往这方面想就遭。

我们可以先把每个局面的状态通过一棵满二叉树表示出来,大概就是这样的:

这样子的话我们可以直接暴力按树深搜,然后根据深度的奇偶来取 max,min

code

ll dfs(ll dept,vector <ll> &w){ //按树深搜
	//记录下此层的序列,然后直接暴力判断是不是倍数 
	if(!w.size()) return 0;
	if(dept==m+1){
		ll sum=0;
		for(ll i:w) sum+=i;
		return sum;
	}
	vector <ll> a1,a2;
	for(ll i:w)
		if(!(i%b[dept])) a1.push_back(i);
		else a2.push_back(i);
	ll ls=dfs(dept+1,a1);
	ll rs=dfs(dept+1,a2);
	if(dept&1) return min(ls,rs);
	else return max(ls,rs);
}

然而这道题 m 高达 2e5n2e4,搜索搜个屁。但是,我们可以发现 2e5 的序列画出来的树也最多只有28层,如果再往下画那么节点就会全是0

那么直接大胆特判:当 m28 时直接输出0

然后就做完了?真的非常神奇,这种思维题真的得好好思考。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=114514,M=1919810;
ll n,m,a[N],b[N];
vector <ll> w;
ll dfs(ll dept,vector <ll> &w){ //按树深搜
	//记录下此层的序列,然后直接暴力判断是不是倍数 
	if(!w.size()) return 0;
	if(dept==m+1){
		ll sum=0;
		for(ll i:w) sum+=i;
		return sum;
	}
	vector <ll> a1,a2;
	for(ll i:w)
		if(!(i%b[dept])) a1.push_back(i);
		else a2.push_back(i);
	ll ls=dfs(dept+1,a1);
	ll rs=dfs(dept+1,a2);
	if(dept&1) return min(ls,rs);
	else return max(ls,rs);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],w.push_back(a[i]);
	for(int i=1;i<=m;++i) cin>>b[i];
	if(m>28){
		cout<<0; //堪比不可以总司令的特判 
		return 0; //树的高度顶多为log层,之后的局面都必定为0 
	}
	cout<<dfs(1,w);
	return 0;
}

T3

link

首先16pts的暴力,直接枚举删点的点,删掉之后 O(n) 看最大连通块大小,总复杂度 O(n2),还有在建图的时候也是直接线性筛一遍然后直接 O(n2) 暴力建图。

代码不放了,人人都会。结果我线性筛只筛到 1e6 然后挂八分

然后是正解:

其实求答案的过程很简单,先用并查集找到最大的连通块,再用tarjan求出割点,再分别来看这些割点的答案,非常套路。

但是这个 O(n2) 的建边优化成 O(nlog2V) 就很费解。

题解说的:

对于更大的数据规模,上述方法中建边的效率仍然不足,考虑优化建边。
我们先定义一个数是单位合数,当且仅当它能够表示为 2 个质数的乘积。
考察联通的性质。如果两个点之间存在一条边,那么可以建一个虚拟节点(这个节点应是两个点的单位合数),向两个点连边,这跟原图在联通性上是等价的。
因此我们定义单位合数为两个质数的乘积,数量为 O(log2V) 级别的, 其中 V 是值域。
若任意两个数的 gcd 为合数,那么必定有至少一个单位合数同时是两者的因数,于是它们通过这个单位合数联通。
此时边的数量就是 O(nlog2V) 的了,剩下的按 40% 的做法即可。

评价:不会

题解code

#include<bits/stdc++.h>

using namespace std;
int p1=2000000;
char buf[2000005];
int gc(){
	if(p1>=2000000)fread(buf,1,2000000,stdin),p1=0;
	return buf[p1++];
}
int rd(){
	int x=0;
	char ch=gc();
	while(ch<'0'||ch>'9')ch=gc();
	while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=gc();
	return x;
}
int n,a[100005],mnp[10000005],h[2100005],cnt,ans,fa[2100005],sz[2100005],id[10100005],M;
int dfn[2100005],low[2100005],st[2100005],top,sign,sz2[2100005],ALL;
bitset<10100005> vst;
struct E{int to,nxt;}e[7200005];
void Add(int x,int y){e[++cnt]={y,h[x]},h[x]=cnt;}
int gf(int x){while(fa[x]&&fa[fa[x]])x=fa[x]=fa[fa[x]];if(fa[x])x=fa[x];return x;}
void Merge(int x,int y){
	x=gf(x),y=gf(y);
	if(x^y)fa[x]=y,sz[y]+=sz[x];
}
void Tarjan(int x,int fa){
	dfn[x]=low[x]=++sign,st[++top]=x,sz2[x]=0;
	int mx=0,cc=0;
	for(int i=h[x];i;i=e[i].nxt){
		if(i==(fa^1))continue;
		int y=e[i].to;
		if(!dfn[y]){
			Tarjan(y,i),low[x]=min(low[x],low[y]);
			if(low[y]>dfn[x])assert(st[top]==y),top--,mx=max(mx,sz2[y]),sz2[x]+=sz2[y],cc++;
			else if(low[y]==dfn[x]){
				int t,sum=0;
				do t=st[top--],sz2[x]+=sz2[t],sum+=sz2[t]; while(t!=y);
				cc++,mx=max(mx,sum);
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
	if(x<=n){
		sz2[x]++; 
		if((fa&&cc)||(!fa&&cc>1))ans=min(ans,max(mx,ALL-sz2[x]));
		else ans=min(ans,ALL-1);
	}
}
void Solve(){
	cnt=1,sign=top=0,memset(h,0,sizeof(h)),memset(fa,0,sizeof(fa)),memset(sz,0,sizeof(sz)),memset(dfn,0,sizeof(dfn));
	n=rd();
	for(int i=1;i<=n;i++){
		int p[10]={0},C[10]={0},x;
		x=rd(),sz[i]=1;
		while(x>1){
			int u=mnp[x];
			p[++p[0]]=u;
			while(x%u==0)x/=u,C[p[0]]++;
		}
		for(int j=1;j<=p[0];j++)
			for(int k=j+(C[j]<=1);k<=p[0];k++)
				if(1ll*p[j]*p[k]<=10000000)Add(i,id[p[j]*p[k]]+n),Add(id[p[j]*p[k]]+n,i),Merge(i,id[p[j]*p[k]]+n);
	}
	int mx=0,se=0,mxp=0;
	for(int i=1;i<=M+n;i++){
		if(fa[i]||!sz[i])continue;
		if(sz[i]>mx)se=mx,mx=sz[i],mxp=i;
		else if(sz[i]>se)se=sz[i];
	}
	ans=mx,ALL=mx,Tarjan(mxp,0),cout<<max(ans,se)<<'\n';
}
int main(){
	//freopen("connect.in","r",stdin);
	//freopen("connect.out","w",stdout);
	for(int i=2;i<=10000000;i++){
		if(vst[i])continue;
		mnp[i]=i;
		if(1ll*i*i>10000000)continue;
		int I=i*(1+(i!=2));
		for(int j=i*i;j<=10000000;j+=I)vst[j]=1,mnp[j]=(!mnp[j]?i:mnp[j]);
	}
	for(int i=2;i<=10000000;i++)if(vst[i]&&!vst[i/mnp[i]])id[i]=++M;
	int t=rd();
	while(t--)Solve();
}

T4

link

虚树上算贡献?dsu on tree?线段树合并?

谢谢,都不会写,这个题能出现在NOIP模拟赛里面真是太核离了……

代码先放着,之后会了再来看/ng

学长程序

#include<bits/stdc++.h>
#define N 100010
#define M 100010
#define LL long long
#define ULL unsigned long long
#define DB double
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define tep(u) for(auto v:e[u])
#define INF 0x3f3f3f3f
#define pir pair<int,int>
#define mp(i,j) make_pair(i,j)
#define fi first
#define se second
using namespace std;
template <typename T> inline void read(T &a)
{
	a=0;T w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+(ch^48);ch=getchar();}
	a*=w;
}
template <typename T,typename ...Args> inline
void read(T &x,Args &...args){read(x);read(args...);}
int n,m,k,dfncc,pcc,c[N],dep[N],siz[N],fa[N],son[N],top[N],dfn[N],nid[N],p[N<<1];
int fat[N],s[N],cnt[N],tp;
LL f[N],g[N],A[N],B[N],h[N],tot;
vector<int> e[N],mem[N];
inline void dfs1(int u)
{
	dep[u]=dep[fa[u]]+1;siz[u]=1;
	tep(u) if(v^fa[u])
	{
		fa[v]=u;dfs1(v);siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
inline void dfs2(int u,int tp)
{
	top[u]=tp;nid[dfn[u]=++dfncc]=u;
	if(son[u]) dfs2(son[u],tp);
	tep(u) if(v^fa[u]&&v^son[u]) dfs2(v,v);
}
inline int Lca(int u,int v)
{
	while(top[u]^top[v]) dep[top[u]]>dep[top[v]]?u=fa[top[u]]:v=fa[top[v]];
	return dep[u]<dep[v]?u:v;
}
inline bool cmp(const int &x,const int &y){return dfn[x]<dfn[y];}
inline void build(int c)//虚空建树,名曰虚树 
{
	pcc=tp=0;for(int x:mem[c]) p[++pcc]=x;p[++pcc]=1;
	sort(p+1,p+1+pcc,cmp);pcc=unique(p+1,p+1+pcc)-p-1;
	for(int i=2,lim=pcc;i<=lim;i++) p[++pcc]=Lca(p[i-1],p[i]);
	sort(p+1,p+1+pcc,cmp);pcc=unique(p+1,p+1+pcc)-p-1;
	rep(i,1,pcc)
	{
		for(;tp&&(dfn[s[tp]]>dfn[p[i]]||dfn[p[i]]>=dfn[s[tp]]+siz[s[tp]]);tp--);
		fat[p[i]]=s[tp];s[++tp]=p[i];
	}
}
inline LL C2(int n){return n<=1?0:1ll*n*(n-1)/2;}
#define u p[i]
inline void dfs3(int co)
{
	rep(i,1,pcc) cnt[p[i]]=0;
	per(i,pcc,1)
	{
		cnt[u]+=(c[u]==co);
		f[u]+=C2(cnt[u]);
		g[u]+=1ll*cnt[u]*((int)mem[co].size()-cnt[u]);
		if(fat[u])
		{
			cnt[fat[u]]+=cnt[u];
			f[fat[u]]-=C2(cnt[u]);
			g[fat[u]]-=1ll*cnt[u]*((int)mem[co].size()-cnt[u]);
		}
	}
}
inline void dfs4(int co)
{
	LL sum=0;
	rep(i,1,pcc)
	{
		f[u]=0;cnt[u]=(c[u]==co);
		if(c[u]==co) sum+=A[u];
	}
	per(i,pcc,1) cnt[fat[p[i]]]+=cnt[p[i]];
	rep(i,2,pcc) f[u]=f[fat[u]]+B[fat[u]]*(cnt[fat[u]]-cnt[u]);
	rep(i,1,pcc) if(c[u]==co) h[u]=f[u]+A[u]*((int)mem[co].size()-2)+sum+B[u]*(cnt[u]-1);
}
#undef u
inline void prework()
{
	per(i,n,2) f[fa[nid[i]]]+=f[nid[i]],g[fa[nid[i]]]+=g[nid[i]];
	rep(i,1,n)
	{
		int u=nid[i];A[u]=A[fa[u]]-f[u];
		tep(u) if(v^fa[u]) A[u]+=f[v],B[u]+=f[v];
		g[u]=tot-f[u]-g[u];
		B[u]+=g[u]-A[u]*2;
	}
}
signed main()
{
	read(n,m,k);
	rep(i,1,n) read(c[i]),mem[c[i]].push_back(i);
	rep(i,1,k) tot+=C2(mem[i].size());
	rep(i,2,n)
	{
		int u,v;read(u,v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1);dfs2(1,1);
	rep(i,1,k) build(i),dfs3(i);
	prework();tot=0;
	rep(i,1,k) if(mem[i].size()>1) build(i),dfs4(i);
	rep(i,1,n) tot+=h[i];
	tot/=4;printf("%lld\n",tot);
	while(m--)
	{
		int u;read(u);
		printf("%lld\n",tot-h[u]);
	}
	return 0;
}
posted @   和蜀玩  阅读(26)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示