加载中…

返回上一页

CSP-S模拟15

下发文件和题解

前言

由于当天下午是运动会,开了 3 个多小时,这个反思一直没有补,现在才发出来.

A. 网格图

首先想到的一定是爆搜,用并查集维护原本是 ‘ . ’ 的联通块. 每一次暴力扫一个 k × k 的正方形,记录最小的答案.

然后,怎么优化. 可以发现每一次横向移动只会改变左右四列和上下四个点的情况. 因此可以在每行首次移动时暴力计算,以后每次移动只需要修补贡献即可.

点击查看代码
#include<bits/stdc++.h>
#define ll int
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 501
#define pll pair<ll,ll>
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();return f?-x:x;
}
sinline void write(rll x) { if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10|'0'); }
ll n,k,ans;
ll id[maxn][maxn],f[maxn*maxn],sz[maxn*maxn];
ll fl[maxn*maxn],num[maxn][maxn],sum[maxn][maxn];
char s[maxn][maxn];
sinline ll find(rll x) { if(x^f[x]) f[x]=find(f[x]);return f[x]; }
sinline void unionn(rll x,rll y) { x=find(x);y=find(y);if(x==y) return;f[y]=x;sz[x]+=sz[y]; }
sinline void add(rll i,rll j,rll& x)
{
	if(s[i][j]!='.') return; rll p=find(id[i][j]);
	if(!fl[p]) x+=sz[p]; fl[p]++;
}
sinline void rem(rll i,rll j,rll& x)
{
	if(s[i][j]!='.') return;rll p=find(id[i][j]);
	fl[p]--;if(!fl[p]) x-=sz[p];
}
int main()
{
	n=read();k=read(); for(rll i=1;i<=n;i++) for(rll j=1;j<=n;j++) id[i][j]=n*(i-1)+j,f[id[i][j]]=id[i][j],sz[id[i][j]]=1;for(rll i=1;i<=n;i++) scanf("%s",s[i]+1);
	for(rll i=1;i<=n;i++)
		for(rll j=1;j<=n;j++)
		{
			if(s[i][j]=='.')
			{
				if(s[i-1][j]=='.') unionn(id[i][j],id[i-1][j]); if(s[i][j-1]=='.') unionn(id[i][j],id[i][j-1]);
			}
			else sum[i][j]=1;
			sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		}
	for(rll i=1;i+k-1<=n;i++)
	{
		memset(fl,0,sizeof(fl));
		for(rll x=i-1;x<=i+k;x++) for(rll y=1;y<=k+1;y++) if(((x^i-1)||(y^k+1))&&((x^i+k)||(y^k+1))) add(x,y,num[i][1]);
		for(rll j=2;j<=n-k+1;j++)
		{
			// cout<<i<<' '<<j<<endl;
			num[i][j]=num[i][j-1];
			for(rll l=i;l<=i+k-1;l++) rem(l,j-2,num[i][j]),add(l,j+k,num[i][j]);
			rem(i-1,j-1,num[i][j]); rem(i+k,j-1,num[i][j]);
			add(i-1,j+k-1,num[i][j]); add(i+k,j+k-1,num[i][j]);
		}
	}
	for(rll i=1;i+k-1<=n;i++)
		for(rll j=1;j+k-1<=n;j++)
			ans=max(ans,num[i][j]+sum[i+k-1][j+k-1]-sum[i-1][j+k-1]-sum[i+k-1][j-1]+sum[i-1][j-1]);
	write(ans);
	return 0;
}

B. 保险箱

数论题. 如果一个数 a 在群里、且 gcd(x , n) 在群里,那么 gcd(x , n) 它的倍数也一定全部都在群里.

那么可以推知一个性质:如果数 a 在这个群里,那么 gcd(a , n) 也一定在群里.

那么想到一种方法,就是把所有 a 数组中的数对 ngcd,确定这个数的指数.

那么自然想到 dfs,处理到一个数 x,如果已经进行过操作就 return;,否则删除当前这个数,继续搜索去删这个数的第一个质因子减 1,第二个质因子减 1,…,直至完全删除为止.

点击查看代码
#include<bits/extc++.h>
#define ll long long
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 250001
#define pll pair<ll,ll>
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();return f?-x:x;
}
sinline void write(rll x) { if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10|'0'); }
ll n,nn,k,sq,x;
ll a[maxn],cnt[maxn];
vector<ll> s;
__gnu_pbds::gp_hash_table<ll,bool> fl,mp;
sinline ll gcd(rll x,rll y) { if(!y) return x; return gcd(y,x%y); }
sinline void dfs(rll x,rll sum)
{
	if(x==s.size()) { mp[sum]=1; return; }
	for(rll i=0;i<=cnt[x];i++) dfs(x+1,sum),sum*=s[x];
}
int main()
{
	nn=n=read();k=read();for(rll i=1;i<=k;i++) a[i]=read();x=n=gcd(n,a[k]);sq=sqrt(n);
	for(rll i=1;i<k;i++) fl[gcd(n,a[i])]=1;
	for(rll i=2;i<=sq;i++) if(!(x%i)) { s.push_back(i); while(!(x%i)) x/=i; }
	if(x>1) s.push_back(x);
	for(rg __gnu_pbds::gp_hash_table<ll,bool>::iterator i=fl.begin();i!=fl.end();i++)
	{
		for(rll j=0,t;j<s.size();j++) { t=(*i).first;cnt[j]=0; while(!(t%s[j])) /*cout<<s[j]<<endl,*/cnt[j]++,t/=s[j]; }
		dfs(0,1);
	}
	for(rll i=1;i<=sqrt(n);i++) if((!(n%i))&&(mp.find(i)==mp.end())) { write(nn/i); return 0; }
	for(rll i=sqrt(n);i;i--) if((!(n%i))&&(mp.find(n/i)==mp.end())) { write(nn/(n/i)); break; }
	return 0;
}

C. 追逐

当我们固定一个起点后,把起点作为整个树的根,那么这时每个节点选不选的价值就是它所有儿子的铁球数量加和.

显然洛谷上讲得比我清楚.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 100001
#define pll pair<ll,ll>
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();return f?-x:x;
}
sinline void write(rll x) { if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10|'0'); }
ll n,m,ans;
ll a[maxn],sum[maxn];
ll f[maxn][201],g[maxn][201];
vector<ll> G[maxn];
stack<ll> s;
sinline void dfs(rll x,rll fa)
{
	while(!s.empty()) s.pop(); for(rll i=1;i<=m;i++) f[x][i]=sum[x],g[x][i]=sum[x]-a[fa];
	for(rll i=0;i<G[x].size();i++)
	{
		rll to=G[x][i];if(to==fa) continue;dfs(to,x);
		for(rll j=0;j<=m;j++) ans=max(ans,f[x][j]+g[to][m-j]);
		for(rll j=1;j<=m;j++)
			f[x][j]=max(f[x][j],max(f[to][j],f[to][j-1]+sum[x]-a[to])),
			g[x][j]=max(g[x][j],max(g[to][j],g[to][j-1]+sum[x]-a[fa]));
	}
	for(rll i=1;i<=m;i++) f[x][i]=sum[x],g[x][i]=sum[x]-a[fa];
	for(rll i=0;i<G[x].size();i++) { rll to=G[x][i];if(to==fa) continue;s.push(to); }
	while(!s.empty())
	{
		rll t=s.top();s.pop(); for(rll j=0;j<=m;j++) ans=max(ans,f[x][j]+g[t][m-j]);
		for(rll j=1;j<=m;j++)
			f[x][j]=max(f[x][j],max(f[t][j],f[t][j-1]+sum[x]-a[t])),
			g[x][j]=max(g[x][j],max(g[t][j],g[t][j-1]+sum[x]-a[fa]));
	}
}
int main()
{
	n=read();m=read();for(rll i=1;i<=n;i++) a[i]=read();
	for(rll i=1,x,y;i<n;i++) x=read(),y=read(),G[x].push_back(y),G[y].push_back(x),sum[x]+=a[y],sum[y]+=a[x];
	dfs(1,0);write(ans);
	return 0;
}

D. 字符串

把题目转化一下,就是把 C 换成 1,把 T 换乘 -1,那么题目要求的就是前缀和非负.

那么有一种思路,就是从前往后扫,遇到 -1 的前缀和就删除. 这样做就可以保证删的位置更靠后,也就是最优的. 加上后缀和,就可以使用扫描线,左端点从大到小扫,右端点加入线段树,查询区间最小值. 这样一分析来,其实就是让求区间的总和减去其最大子段和.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 500001
#define pll pair<ll,ll>
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();return f?-x:x;
}
sinline void write(rll x) { if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10|'0'); }
struct node
{
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
	ll lsum,rsum,sum,ans;
	inline friend node operator+(rg node a,rg node b) { return (node) { max(0ll,max(a.lsum,a.sum+b.lsum)),max(0ll,max(a.rsum+b.sum,b.rsum)),a.sum+b.sum,max(a.ans+b.sum,max(b.ans+a.sum,a.lsum+b.rsum)) }; }
	inline friend void operator+=(rg node& a,rg node b) { a=a+b; }
}t[maxn<<2];
ll n,q,l,r;
ll num[maxn];
char s[maxn];
#define pushup(rt) t[rt]=t[ls(rt)]+t[rs(rt)]
sinline void build(rll rt,rll l,rll r)
{
	if(l==r) { t[rt]=(node){max(num[l],0ll),max(num[l],0ll),num[l],max(num[l],0ll)}; return; }
	rll mid=(l+r)>>1;build(ls(rt),l,mid);build(rs(rt),mid+1,r);pushup(rt);
}
sinline node query(rll rt,rll l,rll r,rll x,rll y)
{
	if(x<=l&&r<=y) return t[rt]; rll mid=(l+r)>>1;
	if(x<=mid&&y>mid) return query(ls(rt),l,mid,x,y)+query(rs(rt),mid+1,r,x,y);
	if(x<=mid) return query(ls(rt),l,mid,x,y); return query(rs(rt),mid+1,r,x,y);
}
int main()
{
	n=read();scanf("%s",s+1);for(rll i=1;i<=n;i++) num[i]=(s[i]^'C')?1:-1;build(1,1,n);q=read();
	while(q--) l=read(),r=read(),write(query(1,1,n,l,r).ans),putn;
	return 0;
}
posted @ 2022-09-30 19:15  1Liu  阅读(48)  评论(3编辑  收藏  举报