A 商品
对于一个数对 \((x,y)[x<y]\),无论怎样选择区间,这个数对的贡献都是 \(y-x\),那么考虑对于所有数对,把 \(x\) 和 \(y\) 分别放到一起,排好序。如果当前区间确定,在两个数组二分一下,前缀和就能处理出答案。
显然区间越大越好,所以只考虑区间左端点,考虑只对于点对 \((x,y)\) 来说,\(l\le x\) 相同,\(r\ge y\) 相同,所以可以取到 \(x\) 和 \(y-d\) 两个左端点。一次检查每个端点计算答案即可。
赛时端点没有考虑右边的贡献,并且一开始想数据结构,后来发现优先队列更好,然后就没思考性质,80pts,但是确实是对的。
直接大力分讨 \(x\),\(y\) 与区间的关系,拿优先队列存储,然后根据单调性一个一个扔,巨麻烦。
#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=4e5+10;
int n,d,a[N],temp[N],ans=0,ans2,ans3,ans4,ans5,size,num,Ne[N];
int l=0,r=0,L,R;
bool vis[N];
struct NODE{
int x,y,id;
bool operator<(const NODE &a)const{return x>a.x;}
};
struct WU{
int x,y,id;
bool operator<(const WU&a)const{return y>a.y;}
};
std::priority_queue<NODE> q1,q2,q3;
std::priority_queue<WU> qf,q4,q5;
signed main(){
freopen("goods.in","r",stdin);freopen("goods.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read(),d=read();
for(int i=1;i<=n;++i)temp[i]=a[i]=read();
std::sort(temp+1,temp+n+1);
l=r=temp[1];L=R=1;
int top=0;
for(int i=1;i<n;++i){
int x=a[i],y=a[i+1];if(x>y)std::swap(x,y);
Ne[++top]=x,Ne[++top]=y-d;
q1.push({x,y,i});
}
std::sort(Ne+1,Ne+top+1);
while(L<=top){
l=Ne[L];
l=std::max(l,0ll);r=l+d;
while(!q1.empty()){
auto it=q1.top();q1.pop();
int x=it.x,y=it.y,id=it.id;
if(x>=r&&y>=r){
q1.push({x,y,id});break;
}
if(x>=l&&x<=r&&y>=r){
q2.push({x,y,id});ans2+=x;size++;
qf.push({x,y,id});
vis[id]=1;
continue;
}
if(x>=l&&x<=r&&y>=l&&y<=r){
q3.push({x,y,id});ans3+=y-x;
continue;
}
if(x<=l&&y>=r){
q4.push({x,y,id});ans4++;
continue;
}
if(x<=l&&y>=l){
q5.push({x,y,id});ans5+=y;
continue;
}
}
while(!q2.empty()){
auto it=q2.top();q2.pop();
int x=it.x,y=it.y,id=it.id;
if(!vis[id])continue;
ans2-=x;size--;vis[id]=0;
if(x>=l&&x<=r&&y>=r){
q2.push({x,y,id});ans2+=x;size++;
vis[id]=1;
break;
}
if(x>=l&&x<=r&&y>=l&&y<=r){
q3.push({x,y,id});ans3+=y-x;
continue;
}
if(x<=l&&y>=r){
q4.push({x,y,id});ans4++;
continue;
}
if(x<=l&&y>=l&&y<=r){
q5.push({x,y,id});ans5+=y;
continue;
}
}
while(!qf.empty()){
auto it=qf.top();qf.pop();
int x=it.x,y=it.y,id=it.id;
if(!vis[id])continue;
ans2-=x;size--;vis[id]=0;
if(x>=l&&x<=r&&y>=r){
qf.push({x,y,id});ans2+=x;size++;
vis[id]=1;
break;
}
if(x>=l&&x<=r&&y>=l&&y<=r){
q3.push({x,y,id});ans3+=y-x;
continue;
}
}
while(!q3.empty()){
auto it=q3.top();q3.pop();
int x=it.x,y=it.y,id=it.id;ans3-=y-x;
if(x>=l&&x<=r&&y>=l&&y<=r){
q3.push({x,y,id});ans3+=y-x;
break;
}
if(x<=l&&y>=r){
q4.push({x,y,id});ans4++;
continue;
}
if(x<=l&&y>=l&&y<=r){
q5.push({x,y,id});ans5+=y;
continue;
}
}
while(!q4.empty()){
auto it=q4.top();q4.pop();
int x=it.x,y=it.y,id=it.id;ans4--;
if(x<=l&&y>=r){
q4.push({x,y,id});ans4++;
break;
}
if(x<=l&&y>=l&&y<=r){
q5.push({x,y,id});ans5+=y;
continue;
}
}
while(!q5.empty()){
auto it=q5.top();q5.pop();
int x=it.x,y=it.y,id=it.id;ans5-=y;
if(x<=l&&y>=l&&y<=r){
q5.push({x,y,id});ans5+=y;
break;
}
}
num=q5.size();
int res=size*r-ans2+ans3+ans4*(r-l)+ans5-num*l;
ans=std::max(ans,res);
L++;
}
std::cout<<ans<<'\n';
}
B 价值
究极分讨树形 DP,但是状态对了也挺好写。
如果没有神必叶子边,直接 DP 就做完了,考虑给 DP 加上一些限制,\(f_{u,0/1,0/1,0/1}\) 表示在 \(u\) 的子树中,\(u\) 是否匹配,最左端的叶子是否和前一个叶子匹配,最右端的叶子是否和后一个叶子匹配。答案就是 \(f_{u,0,0,0}+f_{u,0,1,1}+f_{u,1,0,0}+f_{u,1,1,1}\)。
考虑子树合并的转移,对于所有节点,有初始状态 \(f_{u,0,0,0}=1\),对于叶子,有 \(f_{u,1,0,1}=f_{u,1,1,0}=1\)。对于第一个儿子节点,直接继承转移。
对于一般情况,子节点的状态必须要与合并状态的右端点状态相匹配,知道这个之后就好写多了,具体转移看代码(最短解)。
#include<bits/stdc++.h>
#define fo(i,s,j) for(int i=s;i<=j;++i)
#define int long long
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1e5+10,mod=998244353;
inline int mo(int x){return x<mod?x:x%mod;}
int n,f[N][2][2][2],la[2][2][2],ans;
std::vector<int> e[N];
inline void dfs(int u){
f[u][0][0][0]=1;
if(!e[u].size()){f[u][1][1][0]=f[u][1][0][1]=1;return ;}
for(int i=0;i<e[u].size();++i){
int v=e[u][i];dfs(v);
if(!i){fo(l,0,1)fo(r,0,1)f[u][0][l][r]=mo(f[v][0][l][r]+f[v][1][l][r]),f[u][1][l][r]=f[v][0][l][r];continue;}
memcpy(la,f[u],sizeof(la));
fo(l,0,1)fo(r,0,1)f[u][0][l][r]=mo(mo(la[0][l][r]*(f[v][0][r][r]+f[v][1][r][r]))+mo(la[0][l][r^1]*(f[v][0][r^1][r]+f[v][1][r^1][r])));
fo(l,0,1)fo(r,0,1)f[u][1][l][r]=mo(mo(la[0][l][r]*f[v][0][r][r])+mo(la[0][l][r^1]*f[v][0][r^1][r])+mo(la[1][l][r]*(f[v][0][r][r]+f[v][1][r][r]))+mo(la[1][l][r^1]*(f[v][0][r^1][r]+f[v][1][r^1][r])));
}
}
signed main(){
freopen("value.in","r",stdin);freopen("value.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();fo(i,2,n){int x=read();e[x].emplace_back(i);}dfs(1);
std::cout<<mo(f[1][0][0][0]+f[1][0][1][1]+f[1][1][0][0]+f[1][1][1][1])<<'\n';
}
C 货币
网络流切糕模型,以后系统地学一下。
D 资本
生成函数,以后系统地学一下。
总结
这场成策略大师了,直接想优先队列能做后就不想别的了,你真以为正解需要六个优先队列,然后对拍调试三个小时,后面暴力都没时间拼。
T2 这种带点构造博弈感觉的树形 DP 就一点都不会,看题解也看不明白,该多练 DP 了。