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号