CSP 后多校十三
A. 嗑瓜子
简单的概率与期望,有很多方法来做.
可以选择正推概率,最后乘一个操作次数就是贡献,思路来自沈老师.
可以选择一遍推概率,一遍推步数( 其实没有什么卵用 ),从而得到期望,思路来自 \(fengwu\).
可以选择倒推期望,从结果入手,最后回到状态起点就是答案,思路来自 \(Amx\).
A_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
#define int long long
#define lf long double
#define pb push_back
#define mp make_pair
#define lb lower_bound
#define ub upper_bound
#define lbt(x) ((x)&(-(x)))
#define Fill(x,y) memset(x,y,sizeof(x))
#define Copy(x,y) memcpy(x,y,sizeof(x))
#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
for(;!isdigit(ch);ch=getchar()) cit=(ch=='-');
for(;isdigit(ch);ch=getchar()) w=(w<<3)+(w<<1)+(ch^48);
return cit?(-w):w;
};
} using namespace BSS;
const int N=5e3+21,mod=998244353;
int m,n,ans;
int inv[N<<2];
int dp[N][N];
signed main(){
File(eat);
n=read(),inv[1]=1;
for(int i=2;i<=5e3;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;i++){
for(int j=0;j<=n*2;j++){
dp[i][j]=(dp[i-1][j+2]*i%mod*inv[i+j]%mod+1)%mod;
if(j>0) dp[i][j]=(dp[i][j]+dp[i][j-1]*j%mod*inv[i+j]%mod)%mod;
}
}
printf("%lld\n",dp[n][0]),exit(0);
}
B. 第 k 大查询
考虑什么东西可以暴力做,什么东西可以优化做.
这个题几乎就是很暴力,直接每次找到左边比自己大的 \(k\) 个数,右边比自己大的 \(k\) 个数,链表维护很方便.
B_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
// #define int long long
#define lf long double
#define pb push_back
#define mp make_pair
#define lb lower_bound
#define ub upper_bound
#define lbt(x) ((x)&(-(x)))
#define Fill(x,y) memset(x,y,sizeof(x))
#define Copy(x,y) memcpy(x,y,sizeof(x))
#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
for(;!isdigit(ch);ch=getchar()) cit=(ch=='-');
for(;isdigit(ch);ch=getchar()) w=(w<<3)+(w<<1)+(ch^48);
return cit?(-w):w;
};
} using namespace BSS;
#define LL long long
const int N=5e5+21;
LL ans;
int m,n;
int val[N],pos[N],le[N],ri[N];
int con[N][2];
multiset<int> s;
vector<int> vec;
signed main(){
File(kth);
n=read(),m=read();
for(int i=1;i<=n;i++) val[i]=read(),pos[val[i]]=i;
for(int i=1;i<=n;i++) con[i][0]=i-1,con[i][1]=i+1;
for(int i=1,lmi=n-m+1;i<=lmi;i++){
int x=pos[i],cl=1,cr=1; le[cl]=x,ri[cr]=x;
while(con[x][0] and cl<m) x=con[x][0],le[++cl]=x;
x=pos[i];
while(con[x][1]<=n and cr<m) x=con[x][1],ri[++cr]=x;
if(cl+cr<=m) break;
// cout<<"cl and cr:"<<cl<<' '<<cr<<endl;
// for(int j=1;j<=cl;j++) cout<<le[j]<<' '; puts("");
// for(int j=1;j<=cr;j++) cout<<ri[j]<<' '; puts("");
for(int j=1;j<=cl;j++){
if(m-j+1>cr) continue;
// cout<<(con[ri[m-j+1]][1]-ri[m-j+1])<<' '<<(le[j]-con[le[j]][0])<<" skr\n";
ans+=1ll*(con[ri[m-j+1]][1]-ri[m-j+1])*(le[j]-con[le[j]][0])*i;
}
x=pos[i];
con[con[x][0]][1]=con[x][1];
con[con[x][1]][0]=con[x][0];
}
printf("%lld\n",ans),exit(0);
}
C. 树上路径
可以显然地发现题目就是让求删掉一条边之后求左右两边联通块的最大直径,然后最大后缀和就可以了.
考虑怎么求,一个很直接的思路就是开一个 \(multiset\),但是发现有用的其实就只有前几个长度,于是维护就好了.
C_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
// #define int long long
#define lf long double
#define pb push_back
#define mp make_pair
#define lb lower_bound
#define ub upper_bound
#define lbt(x) ((x)&(-(x)))
#define Fill(x,y) memset(x,y,sizeof(x))
#define Copy(x,y) memcpy(x,y,sizeof(x))
#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
for(;!isdigit(ch);ch=getchar()) cit=(ch=='-');
for(;isdigit(ch);ch=getchar()) w=(w<<3)+(w<<1)+(ch^48);
return cit?(-w):w;
};
} using namespace BSS;
#define LL long long
const int N=5e5+21;
LL ans;
int m,n,ts;
int head[N],maxn[N];
int f[N][2],g[N][2];
struct I { int u,v,nxt; } e[N<<1];
auto add=[](int u,int v)->void{
e[++ts].u=u,e[ts].v=v,e[ts].nxt=head[u];
head[u]=ts;
};
void sch(int u,int dad){
for(int i=head[u],v;i;i=e[i].nxt){
if((v=e[i].v)==dad) continue;
sch(v,u),f[u][0]=max(f[u][0],f[v][0]);
f[u][0]=max(f[u][0],g[v][0]+g[u][0]+1);
g[u][0]=max(g[u][0],g[v][0]+1);
}
}
void dfs(int u,int dad){
int x=g[u][1],y=-1,z=-1,w=f[u][1];
for(int i=head[u],v;i;i=e[i].nxt){
if((v=e[i].v)==dad) continue;
if(g[v][0]>=x) z=y,y=x,x=g[v][0];
else if(g[v][0]>=y) z=y,y=g[v][0];
else z=max(z,g[v][0]);
}
for(int i=head[u],v;i;i=e[i].nxt){
if((v=e[i].v)==dad) continue;
if(g[v][0]==x) f[v][1]=max(w,y+z+2),g[v][1]=y+1;
else if(g[v][0]==y) f[v][1]=max(w,x+z+2),g[v][1]=x+1;
else if(g[v][0]==z) f[v][1]=max(w,x+y+2),g[v][1]=x+1;
w=max(w,f[v][0]); dfs(v,u);
}
}
signed main(){
File(tree);
n=read(); int u,v;
for(int i=2;i<=n;i++){
u=read(),v=read(),add(u,v),add(v,u);
}
sch(1,0),g[1][1]=-1,dfs(1,0);
for(int i=1;i<=n;i++) f[i][0]++,f[i][1]++;
for(int i=1;i<=n;i++){
maxn[f[i][1]]=max(maxn[f[i][1]],f[i][0]);
maxn[f[i][0]]=max(maxn[f[i][0]],f[i][1]);
}
for(int i=n;i;i--) maxn[i]=max(maxn[i],maxn[i+1]),ans+=1ll*maxn[i];
printf("%lld\n",ans),exit(0);
}
D. 糖
向 dalao 询问了一下思考的方向.
遇到这种买卖策略问题的大概率可能会是 \(O(n)\) 做.
直接做的话很难写,不过有一个 \(O(n^2)\) 的 \(dp\) 还是比较好想的.
考虑怎么优化这个 \(dp\),其实列出式子来,想办法根据图像只选取最优点就可以了.
不过正解的思考方向并不在这里,正解的思考方向其实是一个转化.
考虑到每次买卖的操作都很麻烦,不如转化一下.
- 我们可以每次选择把物品买到满,如果最后剩下了,那么还可以退回去,相当于没买.
- 我们每次吃糖果尽量吃价格低的,这样一定不劣.
- 如果没有卖出这个操作题目会变得会很简单,但是如果有了卖出操作,那么可以想一下什么时候卖出,就是买了便宜的,到后面的位置去卖.
于是转化操作也就抽象为了一些等价的买卖操作.
其实以上都是在构造一些简单的规则,看起来很麻烦,其实仔细想一想还是很自然的.
买后退钱<=>不买,买后改价退掉<=>卖掉
整体上也是一个反悔贪心的过程,只不过巧妙的转化了一些,从而让这个过程更简单.
D_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
#define int long long
#define lf double
#define pb push_back
#define mp make_pair
#define lb lower_bound
#define ub upper_bound
#define Fill(x,y) memset(x,y,sizeof(x))
#define Copy(x,y) memset(x,y,sizeof(x))
#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
while(!isdigit(ch)) cit|=(ch=='-'),ch=getchar();
while(isdigit(ch)) w=(w<<3)+(w<<1)+(ch^48),ch=getchar();
return cit?(-w):w;
};
}using namespace BSS;
#define fi first
#define se second
const int N=2e5+21;
int m,n,cnt,ans;
int a[N],b[N],dis[N];
deque<int> qs;
deque<pair<int,int> > qb;
signed main(){
File(candy);
n=read(),m=read(); int x,y,z;
for(int i=1;i<=n;i++) dis[i]=read();
for(int i=0;i<n;i++) a[i]=read(),b[i]=read();
qb.push_back(mp(0,m)),ans+=a[0]*m,cnt=m;
for(int i=1;i<n;i++){
for(int j=dis[i]-dis[i-1];j;){
pair<int,int> tmp=qb.front(); qb.pop_front();
if(tmp.se<j) j-=tmp.se,cnt-=tmp.se;
else tmp.se-=j,cnt-=j,j=0,qb.push_front(tmp);
}
while(qs.size() and qb.front().fi>qs.front()) qs.pop_front();
while(qs.size() and b[qs.back()]<b[i]) qs.pop_back();
qs.push_back(i),x=0;
while(qb.size() and a[qb.back().fi]>a[i]){
pair<int,int> tmp=qb.back();
if(qs.size()) ans-=max(b[qs.front()],a[tmp.fi])*tmp.se;
else ans-=a[tmp.fi]*tmp.se;
cnt-=tmp.se,qb.pop_back();
}
while(qb.size() and a[i]<=b[qs.front()]){
pair<int,int> tmp=qb.back();
cnt-=tmp.se,ans-=b[qs.front()]*tmp.se,qb.pop_back();
}
qb.push_back(mp(i,m-cnt)),ans+=a[i]*(m-cnt),cnt=m;
}
for(int j=dis[n]-dis[n-1];j;){
pair<int,int> tmp=qb.front(); qb.pop_front();
if(tmp.se<j) j-=tmp.se,cnt-=tmp.se;
else tmp.se-=j,cnt-=j,j=0,qb.push_front(tmp);
}
while(qs.size() and qb.front().fi>qs.front()) qs.pop_front();
while(qb.size()){
pair<int,int> tmp=qb.back();
if(qs.size()) ans-=max(b[qs.front()],a[tmp.fi])*tmp.se;
else ans-=a[tmp.fi]*tmp.se;
qb.pop_back();
}
printf("%lld\n",ans),exit(0);
}