25-暑期-来追梦noip-卷2 总结
开题顺序:A-B-C-D
时间分配:A 1h(1h 怒砍 60pts 也是无敌了)B 30min C 25min D 5min
A
预估 60,实际 60。
把 \(a_i\) 看作价值,\(b_i\) 看作体积,本题转化为背包问题,背包容量显然为 \(100\)。
令 \(dp_{i,j}\) 表示前 \(i\) 次攻击,且魔法值为 \(j\) 的能打掉的最大血量。
在 \(dp_{i,j} \ge 100\) 时取得答案即可。初始全为 \(0\)。
转移:\(dp_{i,j}=\max(dp_{i,j},dp_{i-1,j-a_k+t}+b_k)\),注意条件为 \(j \ge a_k\),还有魔法值要和 \(100\) 取 \(\min\)。
时间复杂度是 \(\mathcal{O}(n^3)\) 的。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+5;
int _,n,t,q;
int a[N],b[N],dp[N][N];
void solve(){
	memset(dp,0,sizeof dp);
	cin>>n>>t>>q;
	for(int i=1;i<=n;i++)
		cin>>a[i]>>b[i];
	a[0]=0,b[0]=1;
	int limit=(100+q-1)/q;
	bool f=0;
	int ans=-1;
	for(int i=1;i<=limit;i++){
		for(int k=0;k<=n;k++){
			for(int j=0;j<=100;j++){
				if(j>=a[k])
					dp[i][min(100ll,j-a[k]+t)]=max(dp[i][min(100ll,j-a[k]+t)],dp[i-1][j]+b[k]);
				if(dp[i][min(100ll,j-a[k]+t)]>=100){
					ans=i,f=1; break;
				}
			}
			if(f) break;
		}
		if(f) break;
	}
	if(f)
		cout<<ans<<'\n';
	else
		cout<<"Loser!\n";
}
signed main(){
	//freopen("T1.in","r",stdin);
	//freopen("T1.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>_;
	while(_--)
		solve();
	return 0;
} 
总结:
- 背包问题关注价值、体积和背包容量。
B
预估 30,实际 30。
看到 \(A^3 \times B=N\) 这种乘积式容易想到质因数分解。
然后我们发现只需要找三次方项即可,时间复杂度最坏是 \(\mathcal{O}(\sqrt{N} \times \ln N)\),但很显然远远跑不满,可以获得 60 pts 的好成绩。
容易发现瓶颈在于筛质数,于是考虑少筛一点。
具体的,我们考虑筛出不超过 \(N^{1/4}\) 的质数。
现在我们就只需要考虑 \(>N^{1/4}\) 的三次方质因子了。
不妨猜想这样的质因子只有一个,事实上这个结论是正确的。
考虑反证法,假设这样的质因子有两个,我们分别令其为 \(x_1,x_2\)。
由于 \(x_1,x_2 > N^{1/4}\),所以有 \(x_1^3x_2^3 > ((N^{1/4})^2)^3=N^{3/2} > N\),这显然不合法,命题得证。
接下来,我们令筛去 \(N^{1/4}\) 以内的质因子后的 \(N\) 为 \(N'\),那个唯一的质因子为 \(x\)。
显然,有 \(N'=x^3 \times p\),其中 \(p\) 为筛出来的三次方质因子的乘积。
又因为 \(p\) 必须是 \(>N^{1/4}\) 的(因为小于等于的已经算过了),所以两部分都大于 \(N^{1/4}\),又不合法了,于是 \(p\) 只能等于 \(1\)。
所以,本题的做法呼之欲出:先筛出 \(N^{1/4}\) 以内的质数,乘到答案里,然后判断最后的 \(N'\) 是否为完全立方数,是的话就乘上那个质因子即可。
需要注意的是,判断是否为完全立方数的部分最好用二分,cbrt 会炸。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e4;
const int M=1e5+5;
int _,tot;
double n;
bool vis[M];
int pr[M];
void E(){
	vis[0]=vis[1]=1;
	for(int i=2;i<=N;i++){
		if(!vis[i]){
			pr[++tot]=i;
			for(int j=i+i;j<=N;j+=i)
				vis[j]=1;
		}
	}
}
void solve(){
	cin>>n;
	int ans=1;
	for(int i=1;i<=tot;i++){
		int cnt=0;
		while(n%pr[i]==0){
			n/=pr[i],cnt++;
			if(cnt==3)
				ans*=pr[i],cnt=0;
		}
	}
	n=(int)n;
	if(cbrt(n)==(int)cbrt(n))
		ans*=cbrt(n);
	cout<<ans<<'\n';
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	//freopen("T2.in","r",stdin);
	//freopen("T2.out","w",stdout);
	cin>>_,E();
	while(_--)
		solve();
	return 0;
}
总结:
- 
乘积式考虑质因数分解。 
- 
复杂度过高时考虑分段处理(根号分治思想)。 
C
预估 30,实际 30。
每次 BFS 跑最短路,可以获得 30 pts 的好成绩。
考虑弱化版问题。如果图退化为一棵树,直接 树上差分 即可。
观察到边很少,最多 \(n+200\),这意味着最多比树多 \(200\) 个点,于是可以对这 \(200\) 个点跑 BFS 然后取 \(\min\) 即可。时间复杂度单 \(\log\) 加上 \(200n\) 的常数。
注意这个方法对于前 \(30\%\) 的数据并不适用,所以需要把两部分拼起来。实现时可以使用 namespace 封装。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,M=2e2+5,K=48;
int n,m,t,cnt;
int Dis[N],dis[M][N],fa[N],dep[N],dp[N][K];
bool vis[N];
vector<int> G[N];
namespace sol1{
	void bfs(int s){
		queue<int> q;
		q.push(s);
		memset(Dis,0x3f,sizeof Dis);
		Dis[s]=0;
		memset(vis,0,sizeof vis);
		while(!q.empty()){
			int cur=q.front();
			q.pop();
			if(vis[cur])
				continue;
			vis[cur]=1;
			for(int i:G[cur]){
				if(Dis[i]>Dis[cur]+1){
					Dis[i]=Dis[cur]+1;
					q.push(i);
				}
			}
		}
	}
	void solve(){
		cin>>m;
		for(int i=1,u,v;i<=m;i++){
			cin>>u>>v;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		cin>>t;
		while(t--){
			int a,b;
			cin>>a>>b;
			bfs(a);
			cout<<Dis[b]<<'\n';
		}
	}
}
namespace sol2{
	void bfs(int s){
		++cnt;
		queue<int> q;
		q.push(s);
		dis[cnt][s]=0;
		memset(vis,0,sizeof vis);
		while(!q.empty()){
			int cur=q.front();
			q.pop();
			if(vis[cur])
				continue;
			vis[cur]=1;
			for(int i:G[cur]){
				if(dis[cnt][i]>dis[cnt][cur]+1){
					dis[cnt][i]=dis[cnt][cur]+1;
					q.push(i);
				}
			}
		}
	}
	void initLCA(int cur,int fa){
		dep[cur]=dep[fa]+1;
			dp[cur][0]=fa;
		for(int i=1;(1<<i)<=dep[cur];i++)
			dp[cur][i]=dp[dp[cur][i-1]][i-1];
		for(int i:G[cur])
			if(i!=fa)
				initLCA(i,cur);
	}
	int queryLCA(int x,int y){
		if(dep[y]>dep[x]) swap(x,y);
		for(int i=20;i>=0;i--)
			if(dep[dp[x][i]]>=dep[y])
				x=dp[x][i];
		if(x==y)
			return x;
		for(int i=20;i>=0;i--)
			if(dp[x][i]!=dp[y][i])
				x=dp[x][i],y=dp[y][i];
		return dp[x][0];
	}
	int fnd(int x){
		return x==fa[x]?x:fa[x]=fnd(fa[x]);
	}
	void solve(){
		cin>>m;
		vector<pair<int,int> > vec;
		for(int i=1;i<=n;i++)
			fa[i]=i;
		memset(dis,0x3f,sizeof dis);
		for(int i=1,u,v;i<=m;i++){
			cin>>u>>v;
			int uu=fnd(u),vv=fnd(v);
			if(uu!=vv){
				G[u].push_back(v);
				G[v].push_back(u);
				fa[uu]=vv;
			}
			else
				vec.push_back({u,v});
		}
		initLCA(1,0);
		for(auto i:vec){
			int u=i.first,v=i.second;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		for(auto i:vec){
			int u=i.first;
			bfs(u);
		}
		cin>>t;
		while(t--){
			int a,b;
			cin>>a>>b;
			int lca=queryLCA(a,b);
			//cout<<lca<<'\n';
			int ans=dep[a]+dep[b]-2*dep[queryLCA(a,b)];
			for(int i=1;i<=cnt;i++)
				ans=min(ans,dis[i][a]+dis[i][b]);
			cout<<ans<<'\n';
		}
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	//freopen("T3.in","r",stdin);
	//freopen("T3.out","w",stdout);
	cin>>n;
	if(n<=1000)
		sol1::solve();
	else
		sol2::solve();
	return 0;
}
总结:
- 
关注数据范围。 
- 
考虑弱化版问题。 
D
预估 10,实际 10。
观察到 \(n \le 20\) 考虑 状压 dp。
容易发现最大前缀有很多重复的机会,于是考虑统计最大前缀的方案数。
既然是计数,就需要一些约束条件。经过分析,可以发现下列性质(令最大前缀和断点为 \(x\)):
- 
\(\forall i \in [1,x],\sum^x_{j=i} a_j \ge 0\)(不然就可以删掉一些前缀使得答案更大) 
- 
\(\forall i \in [x+1,n],\sum ^{i}_{j=x+1} a_j < 0\)(不然就可以添加一些后缀使得答案更大) 
于是,令 \(dp1_i\) 表示二进制状态 \(i\) 下满足性质 \(1\) 的前缀方案数,\(dp2_i\) 表示满足性质 \(2\) 的后缀方案数,显然这两种状态是互补的,并且可以递推预处理。
令 \(sum_i\) 表示二进制状态 \(i\) 下的 \(\sum a_i\),则答案即为 \(\sum dp1_i \times dp2_{(2^n-1) \oplus i} \times sum_i\)。
时间复杂度 \(\mathcal{O}(n2^n)\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=21;
const int MOD=998244353;
int n;
int a[N],sum[1<<N],dp1[1<<N],dp2[1<<N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	//freopen("T4.in","r",stdin);
	//freopen("T4.out","w",stdout);
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<n;j++)
			if(((i>>j)&1))
				sum[i]=(sum[i]+a[j])%MOD;
	dp1[0]=dp2[0]=1;
	for(int i=1;i<(1<<n);i++){
		for(int j=0;j<n;j++){
			if((i>>j)&1){
				if(sum[i]<0)
					dp2[i]=(dp2[i]+dp2[i^(1<<j)])%MOD;
				if(sum[i^(1<<j)]>=0)
					dp1[i]=(dp1[i]+dp1[i^(1<<j)])%MOD;
			}
		}
	}
	int all=(1<<n)-1,ans=0;
	for(int i=0;i<(1<<n);i++)
		ans=(ans+((dp1[i]%MOD*dp2[all^i]%MOD)%MOD*sum[i]%MOD)%MOD)%MOD;
	cout<<(ans%MOD+MOD)%MOD;
	return 0;
}
总结:
- 
关注数据范围。 
- 
关注样例。 
结语
成绩:60+30+30+10=130,并未挂分。
问题:背包模型不熟练,对数论问题不够敏感,题目性质挖掘不够深入。
方案:
- 
加强背包模型、质因数分解技巧的掌握程度。 
- 
对于数据范围、样例多进行观察和模拟,挖掘性质。 
以上。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号