Logo zrl123456 的博客

博客

【2024暑假】-普及6 讲解

...
zrl123456
2025-12-01 12:51:28
我咋啥也不会/ll

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-13 13:52:38

P8649 [蓝桥杯 2017 省 B] k 倍区间

P8649 [蓝桥杯 2017 省 B] k 倍区间

题目考察:前缀和,桶。
题目简述:
给你一个序列 $\{a_n\}$,求:
$$\sum_{i=1}^n\sum_{j=i}^n[(\sum_{x=i}^ja_x)\bmod k=0]$$ 数据范围:

  • $1\le n,k,a_i\le 10^5$

设 $sum_i=\sum_{j=1}^ia_j$,由于 $(sum_r-sum_{l-1})\bmod k=0$,所以 $sum_r\equiv sum_{l-1}\pmod k$,那么我们开一个桶,每次将 $t_{sum_i\bmod k}$ 加 $1$,累加答案即可。

时间复杂度为 $\Theta(n)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define endl '\n'
#define inl inline
#define reg register
#define rep(i,x,y) for(reg int i=x;i<=y;++i)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
const int N=1e5+5;
int n,k,a[N],sum[N],mp[N],ans;
signed main(){
	fst;
	cin>>n>>k;
	rep(i,1,n) cin>>a[i];
	rep(i,1,n) sum[i]=(sum[i-1]+a[i])%k;
	++mp[0];
	rep(i,1,n) ans+=mp[sum[i]]++;
	cout<<ans;
	return 0;
} 

P8247 皇室战争

P8247 皇室战争

题目考察:斜率。
题目简述:
有一位战士,他有一些箭,他要射死一些敌人。他的箭能穿透,求他最少要射几次。
数据范围:

  • $1\le n\times m\le 10^6$

我们要求战士到敌人的斜率,斜率相同的就是能一箭射死的。
由于斜率要求浮点数,浮点数的精度可能会出问题,我们需要像一种其他的方法。
如果 $\displaystyle\frac{a}{b}=\frac{c}{d}$,则 $a\div \gcd(a,b)=c\div\gcd(c,d),b\div\gcd(a,b)=d\div\gcd(c,d)$,那么我们将其除以 $|\gcd(a,b)|$ 即可。

时间复杂度为 $\Theta(nm\log_2(nm))$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define endl '\n'
#define inl inline
#define reg register
#define rep(i,x,y) for(reg int i=x;i<=y;++i)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define pb push_back
#define pii pair<int,int>
#define prr make_pair
using namespace std;
const int N=1e6+5;
int n,m,sx,sy,k;
vector<char>g[N];
char ch;
map<pii,bool>mp;
signed main(){
	fst;
	cin>>n>>m;
	rep(i,1,n) g[i].pb(' ');
	rep(i,1,n)
		rep(j,1,m){
			cin>>ch;
			g[i].pb(ch);
		}
	rep(i,1,n)
		rep(j,1,m)
			if(g[i][j]=='S'){
				sx=i;
				sy=j;
			}
	rep(i,1,n)
		rep(j,1,m)
			if(g[i][j]=='K'){
				k=abs(__gcd(i-sx,j-sy));
				mp[prr((i-sx)\/k,(j-sy)\/k)]=1;
			}
	cout<<mp.size();
	return 0;
} 

P1131 [ZJOI2007] 时态同步

P1131 [ZJOI2007] 时态同步

题目考察:树,dp,dfs。
题目简述:
给你一颗以 $s$ 为根的树,有一种操作:

  • 选择一条边,将其边权增加 $1$。

求使 $s$ 节点到所有叶子节点的距离都相等的操作数。
数据范围:

  • $1\le n\le 5\times 10^5$

首先我们肯定可以通过一遍 dfs 得出以任意一点为根的子树的最大的 $s$ 节点到子树内叶子节点的距离($mx_u$),那么如果 $mx_u<mx_s$,说明在这个点和其父节点的这条边上操作 $mx_s-mx_u$ 次是最合适的,这样我们再来一遍 dfs 就可以了。

时间复杂度为 $\Theta(n)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define endl '\n'
#define inl inline
#define reg register
#define rep(i,x,y) for(reg int i=x;i<=y;++i)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define pb push_back
#define pii pair<int,int>
#define prr make_pair
#define at(v,x) for(reg auto v:x)
using namespace std;
const int N=5e5+5;
struct node{
	int v,w;
	inl node(){
		v=w=0;
	}
	inl node(int v,int w):v(v),w(w){}
};
vector<node>g[N];
int n,s,u,v,w,mx[N],c[N],ans;
bool vis[N];
inl void dfs(int u,int t){
	vis[u]=1;
	mx[u]=t;
	at(x,g[u])
		if(!vis[x.v]){
			dfs(x.v,t+x.w);
			mx[u]=max(mx[u],mx[x.v]);
		}
	vis[u]=0;
}
inl void dfs2(int u){
	vis[u]=1;
	at(x,g[u])
		if(!vis[x.v]){
			mx[x.v]+=c[u];
			c[x.v]+=c[u];
		}
	c[u]=0;
	at(x,g[u])
		if(!vis[x.v]){
			c[x.v]+=mx[s]-mx[x.v];
			ans+=mx[s]-mx[x.v];
			mx[x.v]=mx[s];
			dfs2(x.v);
		}
	vis[u]=0;
}
signed main(){
	fst;
	cin>>n>>s;
	rep(i,2,n){
		cin>>u>>v>>w;
		g[u].pb(node(v,w));
		g[v].pb(node(u,w));
	}
	dfs(s,0);
	dfs2(s);
	cout<<ans;
	return 0;
} 

P8270 [USACO22OPEN] Subset Equality S

P8270 [USACO22OPEN] Subset Equality S

题目考察:dp,状态压缩,字符串。
题目简述:
给你两个字符串 $s,t$,$q$ 次询问,每次给定一个字符串 $op$,求若 $s$ 和 $t$ 只包含 $op$ 里面的字符,$s$ 和 $t$ 是否相等。
数据范围:

  • $1\le |s|,|t|,q\le 10^5$
  • $1\le |op|\le r$($r=18$)
  • $\forall i\in[1,|s|],\forall j\in[1,|t|],\forall k\in[1,|op|],s_i,t_j,op_k$ 都是 ar 的字符。

这个题有一个结论:若 $|op|\ge 3$ 且他的任意子序列的方案是合法的,则他就是合法的。
下面我们来证明:
两个字符串:
$$\text{abc},\text{acb}$$ 则 $\text{bc},\text{cb}$ 方案不是合法的。
所以我们暴力跑出 $|op|\le 2$ 的合法性,然后进行状压 dp 即可。

时间复杂度为 $\Theta(r^2(n+m)+2^rr+q)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define endl '\n'
#define inl inline
#define reg register
#define rep(i,x,y) for(reg int i=x;i<=y;++i)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define pb push_back
#define pii pair<int,int>
#define prr make_pair
#define at(v,x) for(reg auto v:x)
using namespace std;
const int N=25,M=(1<<18)+5;
string s,t,tmps,tmpt,op;
int q,n,m,sum[N],tmp;
bool dp[M];
signed main(){
	fst;
	cin>>s>>t>>q;
	n=s.size();
	m=t.size();
	s=' '+s;
	t=' '+t;
	rep(i,1,n) ++sum[s[i]-'a'+1];
	rep(i,1,m) --sum[t[i]-'a'+1];
	rep(i,1,18) dp[1ll<<i-1]=!sum[i];
	rep(i,1,18)
		rep(j,i+1,18){
			tmps=tmpt="";
			rep(k,1,n)
				if(s[k]==i+'a'-1||s[k]==j+'a'-1) tmps+=s[k];
			rep(k,1,m)
				if(t[k]==i+'a'-1||t[k]==j+'a'-1) tmpt+=t[k];
			dp[1ll<<i-1|1ll<<j-1]=tmps==tmpt;
		}
	rep(i,0,(1ll<<18)-1)
		if(__builtin_popcount(i)>=3){
			dp[i]=1;
			rep(j,1,18)
				if(i>>j-1&1) dp[i]&=dp[i&~(1ll<<j-1)];
		}
	rep(i,1,q){
		tmp=0;
		cin>>op;
		at(ch,op) tmp|=1ll<<ch-'a';
		if(dp[tmp]) cout<<"Y";
		else cout<<"N";
	}
	return 0;
} 

P6280 [USACO20OPEN] Exercise G 讲解

P6280 [USACO20OPEN] Exercise G

题目考察:数学,数论,素数筛,dp。
题目简述:
给你一个 $n$,求满足下列条件的数 $k$ 的和对 $m$ 取模的结果:

  • 有一个排列 $\{a_n\}$,$k$ 个新排列 $\{b_n\}$,$b_{0,i}=i$,使 $b_{l,a_i}=b_{l-1,i}$,最后使 $\forall i\in[1,n],b_{0,i}=b_{k,i}$。

数据范围:

  • $1\le n\le 10^4$
  • $1\le m\le 10^9+7$
  • $m\in\text{prime}$

假设相邻两个排列之间有一些置换群,例如 $\{1,2,3,4,5\}$ 和 $\{2,3,1,5,4\}$,$1,2,3$ 和 $4,5$ 就是置换群,则设置换群的大小为 $siz_i$,那么会进行 $\text{lcm}_{i=1}^{size}siz_i$ 轮,则:

  • $\displaystyle\sum_{i=1}^{size}siz_i\le n$
  • $\text{lcm}_{i=1}^{size}siz_i=k$

由于 $\text{lcm}$ 只与质数及其幂次有关,所以我们先将这 $cnt$ 个质数筛出来($cnt\pprox \frac{n}{\log_2n}$)。
现在我们就可以进行 dp,设 $dp_{i,j}$ 为进行到第 $i$ 个质数,和已有了 $j$ 的和,易得状态转移方程:
$$dp_{i,j}=\sum_{k=1}^{\lfloor\log_{prime_i}n\rfloor}dp_{i-1,j-prime_i^k}\times prime_i^k$$ 同样的我们可以压掉第一维。

时间复杂度为 $\Theta(n^2)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define endl '\n'
#define inl inline
#define reg register
#define rep(i,x,y) for(reg int i=x;i<=y;++i)
#define per(i,x,y) for(reg int i=x;i>=y;--i)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define pb push_back
#define pii pair<int,int>
#define prr make_pair
#define at(v,x) for(reg auto v:x)
using namespace std;
const int N=1e4+5;
int n,m,cnt,prime[N],f[N],ans,tmp;
bool isprime[N];
inl void init(int x){
	rep(i,2,x)
		if(!isprime[i]){
			prime[++cnt]=i;
			rep(j,2,x\/i) isprime[i*j]=1;
		}
}
signed main(){
	fst;
	cin>>n>>m;
	init(n);
	f[0]=1;
	rep(i,1,cnt)
		per(j,n,prime[i]){
			tmp=prime[i];
			while(tmp<=j){
				(f[j]+=f[j-tmp]*tmp%m)%=m;
				tmp*=prime[i];
			}
		}
	rep(j,0,n) (ans+=f[j])%=m;
	cout<<ans;
	return 0;
} 

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。