25-暑期-来追梦noip-卷8 总结
开题顺序:C-A-B(D 没看)
A
预估 60,实际 60。
乍一看,这个题似乎没有任何头绪。
我们从部分分入手,观察一下 \(3\) 个杯子的情况。
假设它们的体积分别为 \(a,b,c\)。
则答案可能为以下三种:
然后我们发现一个惊人的事实:无论如何操作,答案总是一样的。
这样,我们就可以通过模拟得出答案。
然后,方案数显然就是所有方案的总和,即 $\prod^{n}_{i=1} \frac{i \times (i-1)}{2} $。
时间复杂度 \(\mathcal{O}(n)\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e4+5;
const int MOD=1e9+7;
int _,n,op;
int a[N];
void solve(){
	cin>>n>>op;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int ans=0,tmp=0,cnt=1;
	for(int i=1;i<=n;i++)
		ans=(ans+(tmp*a[i])%MOD)%MOD,tmp=(tmp+a[i])%MOD;
	for(int i=2;i<=n;i++)
		cnt=(cnt*((i*(i-1)/2)%MOD))%MOD;
	cout<<ans<<' '<<(op?cnt:0)<<'\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。
因为亲密度显然具有单调性(越大对数越多),所以考虑二分出对数小于 \(k\) 的最大亲密度。
显然 check 的时候需要知道小于当前亲密度的对数有多少。
我们发现这个信息直接算是不好算的,于是考虑正难则反。对于没有颜色之分的情况,前 \(n-x\) 个数都可以和后面的 \(x\) 个数匹配,后 \(x\) 个数则两两匹配,总方案数为 \((n-x) \times x+\frac{x \times (x-1)}{2}\)。
然后要去掉颜色相同的,这个可以用双指针解决。
最后呢,\(l+1\) 显然就是第 \(k\) 对的亲密度。然后我们暴力在里边找出编号的第 \(k\) 对即可。
因此,总的时间复杂度是 \(\mathcal{O}(n \log n)\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5; 
int n,k;
int a[N];
vector<int> num[N];
int calc(int x){
	int ans=(n-x)*x+x*(x-1)/2;
	for(int i=1;i<=n;i++){
		int l=0;
		for(int j=0;j<num[i].size();j++){
			while(l<num[i].size()&&num[i][j]-num[i][l]>x)
				l++;
			ans-=(j-l);
		}
	}
	return ans;
}
signed main(){
    //freopen("T2.in","r",stdin);
	//freopen("T2.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		cin>>a[i],num[a[i]].push_back(i);
	if(calc(n)<k){
		cout<<-1;
		return 0;
	}
	int l=0,r=n+1;
	while(l+1<r){
		int mid=(l+r)>>1;
		if(calc(mid)<k)
			l=mid;
		else
			r=mid;
	}
	int d=l+1,cnt=calc(l);
	for(int i=1;i+d<=n;i++){
		if(a[i]!=a[i+d])
			cnt++;
		if(cnt==k){
			cout<<i<<' '<<i+d;
			return 0;
		}
	}
	return 0;
}
C
预估 30,实际 30。
赛时以为是数论题,尝试用数位 dp 解决未果。
其实完全想复杂了。
我们考虑质因数分解,有:
令 \(S\) 为 \(n\) 的正约数之和,因为每个质因子可以选择 \(0 \sim p_k\) 个,所以有:
首先 \(n\) 的质因子是根号级别的,然后 \(S\) 每一项都是指数级增长的,而 \(n\) 只有 \(2 \times 10^9\),答案不会很多,于是考虑搜索。
具体的,就是一边将 \(n\) 作为 \(S\) 分解出乘积项,一边组装出最终的答案。注意最后可能有一个 \(> \sqrt{n}\) 的大质数需要特判,然后就是注意去重。具体见代码。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=11,V=1e6;
int _,n,tot;
bool vis[V+5];
int prime[V+5];
vector<int> ans;
void E(){
	for(int i=2;i<=V;i++){
		if(!vis[i]){
			for(int j=i+i;j<=V;j+=i)
				vis[j]=1;
		}
	}
	for(int i=2;i<=V;i++)
		if(!vis[i])
			prime[++tot]=i;	
}
bool isp(int x){
	if(x==0||x==1)
		return 0;
	for(int i=2;i*i<=x;i++)
		if(x%i==0)
			return 0;
	return 1;
}
void dfs(int cur,int pos,int sum){
	if(cur==1){
		ans.push_back(sum);
		return;
	}
	if(isp(cur-1)&&cur>prime[pos])
		ans.push_back(sum*(cur-1));
	for(int i=pos;prime[i]*prime[i]<=cur;i++){
		int tmp=prime[i];
		for(int j=prime[i]+1;j<=cur;tmp*=prime[i],j+=tmp)
			if(cur%j==0)
				dfs(cur/j,i+1,sum*tmp);
	}
}
void solve(){
	cin>>n;
	ans.clear();
	dfs(n,1,1);
	cout<<ans.size()<<'\n';
	if(ans.size()){
		sort(ans.begin(),ans.end());
		for(int i:ans)
			cout<<i<<' ';
		cout<<'\n';
	}
}
signed main(){
    //freopen("T3.in","r",stdin);
	//freopen("T3.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	E();
	cin>>_;
	while(_--)
		solve();
	return 0;
}
总结:
- 数学题,要么是结论,要么是搜索。
D
预估 0,实际 25。
一眼 dp。
如何设计状态?显然我们需要一个具有性质的状态。
于是令 \(dp_i\) 表示长度为 \(i\) 的最长上升子序列最后一位的最小值。显然 \(dp_i\) 单调递增。
对于第 \(i\) 位,令其区间为 \([l,r]\)。分两种情况转移:
- 
若 \(dp_{i-1}<l\),说明无交集,直接 \(dp_i =\min(dp_i,l)\)。 
- 
若 \(l \le dp_i <r\),说明有交集,则必须 \(dp_i=dp_{i-1}+1\)。 
初始 \(dp_0=0\),答案 \(\sum^n_{i=1} [dp_i \neq 0]\)。
这样我们得到了一个 \(\mathcal{O}(n^2)\) 的做法,但无法接受。
我们发现,第一种转移就是单点插入,第二种是区间加,考虑使用平衡树维护之。
具体而言,我们每次插入一个 \(dp_l\),然后删掉第一个 \(\ge r\) 的 \(dp\) 值,这样就可以放心地进行区间加了。
然后答案显然就是最后平衡树里边元素个数。时间复杂度 \(\mathcal{O}(n \log n)\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n,tot,root,all;
struct TREE{
	int ls,rs,siz,rnk,val,tag;
}tree[N];
int addnode(int val){
	tree[++tot].val=val;
	tree[tot].rnk=rand();
	tree[tot].ls=tree[tot].rs=0;
	tree[tot].tag=0;
	return tot;
}
void pushdown(int k){
	if(!tree[k].tag)
		return;
	tree[k].val+=tree[k].tag;
	tree[tree[k].ls].tag+=tree[k].tag;
	tree[tree[k].rs].tag+=tree[k].tag;
	tree[k].tag=0;
}
void split(int k,int &a,int &b,int val){
	if(!k){
		a=b=0;
		return;
	}
	pushdown(k);
	if(tree[k].val<=val)
		a=k,split(tree[k].rs,tree[k].rs,b,val);
	else
		b=k,split(tree[k].ls,a,tree[k].ls,val);
}
void merge(int &k,int a,int b){
	if(!a||!b){
		k=a+b;
		return;
	}
	pushdown(a),pushdown(b);
	if(tree[a].rnk<=tree[b].rnk)
		k=a,merge(tree[k].rs,tree[k].rs,b);
	else
		k=b,merge(tree[k].ls,a,tree[k].ls);
}
void delfir(int &k){
	if(tree[k].ls)
		delfir(tree[k].ls);
	else
		k=tree[k].rs;
}
void ins(int l,int r){
	int x,y,z;
	split(root,x,y,l);
	split(y,y,z,r);
	tree[y].tag++;
	if(z)
		delfir(z);
	else
		all++;
	merge(y,y,z);
	merge(y,addnode(l),y);
	merge(root,x,y);
}
signed main(){
    //freopen("T4.in","r",stdin);
	//freopen("T4.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	srand(time(0));
	int n;
	cin>>n;
	for(int i=1,l,r;i<=n;i++)
		cin>>l>>r,ins(l,r);
	cout<<all;
	return 0;
}
总结:
- 
dp 状态的设计,注意转换角度、挖掘性质。 
- 
单点插入、区间修改考虑平衡树。 
结语
成绩:60+30+30+25=145。
问题:对于诈骗题不够敏感,数学题、计数题套路没见过,dp 状态设计没有切换角度。
方案:同上总结。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号