DP练习1题解
这是 2023.3.7 DP题单十个题的题解。
T1 Colored Subgraphs (CF1796E)
简要题意:给定一棵树,你要对树进行链剖分,必须剖成上下竖直的链,求剖后最短链的长度最大值。
最小值最大,考虑二分最后的答案,显然满足单调性,于是转换成判定性问题:
能不能用长度不小于 的链剖分题目给定的树。
用 DP 来进行 check,先随意钦定一个根进行 DP,例如为 。具体在这之后需不需要换根,如何换根稍后会讲述。
设 表示 下方挂着的最短链长(包括 自己)。
显然叶子结点的 为 ,转移时 。
然后还需要维护一个 表示 对应的那条链的底端叶子结点编号。
叶子结点的 为它自身,转移则跟随 转移即可。
如果记当前二分的长度为 ,那么如果 有两个儿子的 都小于 ,这种局面肯定是非法的。
因此我们还需要临时记录一个次小链长,如果其小于 则 check 为 。
还有一种情况,根节点的 小于 ,也是非法的。
如果我们随意钦定的 已经能为 ,这自然是最好的,check 可以直接返回 ,但如果为 ,并不代表 check 为 ,有可能以其他点为根是可能为 的,怎么办呢?
对于使我们退出的节点 ,无非两种情况。
先看第一种:有两个 小于 的孩子。这种情况下,只有选择这两条链上的点做根才有可能使 check 为 。而且并非链上的所有点都有可能,只有以最低端的叶子结点为新根时才有机会使剖分满足大于等于 的限制。
可以这么想:如果我们选择这两个链上方的点为根,这两个链的结构将不会发生改变,换根无效;如果选择下方的点为根,记其为 ,若 能为 ,则选择之前两条链的底端叶子结点也一定为 ,换根不优。
因此我们在 return false 之前记录两个 分别为当前的节点的最小和次小链的底端节点编号,再以这两个 进行 dfs,如果其中有部分返回 ,check 返回 ,如果依然全部返回 ,那么 check 也就的确为 了。
至此直接 二分 + check 即可,代码:
// Problem: CF1796E Colored Subgraphs
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1796E
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 200005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
vector<int> e[MAXN];
int dep[MAXN],FailSon1,FailSon2,MinChild[MAXN];
int mid;
bool fg;
int rt;
bool dfs(int u,int fath){
int Minx=INT_MAX,Dinx=INT_MAX,DinChild=-1;
for(auto v:e[u]){
if(v!=fath){
if(!dfs(v,u)) return false;
if(dep[v]<Minx){
Dinx=Minx;
DinChild=MinChild[u];
Minx=dep[v];
MinChild[u]=MinChild[v];
}else if(dep[v]<Dinx){
Dinx=dep[v];
DinChild=MinChild[v];
}
}
}
dep[u]=(Minx==INT_MAX?0:Minx)+1;
if(Minx==INT_MAX) MinChild[u]=u;
if(Dinx<mid){
FailSon1=MinChild[u];
FailSon2=DinChild;
return false;
}
if(u==rt && dep[u]<mid){
FailSon1=MinChild[u];
FailSon2=DinChild;
return false;
}
return true;
}
bool check(){
rt=1;
if(dfs(rt,0)) return true;
rt=FailSon1;
int tmp=FailSon2;
if(rt!=-1) if(dfs(rt,0)) return true;
rt=tmp;
if(rt!=-1) if(dfs(rt,0)) return true;
return false;
}
signed main(){
int T=RIN;
while(T--){
int n=RIN;
for(int i=1;i<=n;i++) e[i].clear(),MinChild[i]=-1;
fg=true;
foru(i,1,n-1){
int u=RIN,v=RIN;
e[u].push_back(v);
e[v].push_back(u);
}
int l=0,r=MAXN,ans=0;
while(l<=r){
mid=(l+r)>>1;
if(check()){
ans=mid;
l=mid+1;
}else{
r=mid-1;
}
}
printf("%d\n",ans);
}
return 0;
}
T2 Maximum Subarray (CF1796D)
简要题意:给定一个序列,让你选择其中的 个数 ,剩下的 ,求修改后的最大子段和。
DP。设 表示以第 个数结尾,已经进行了 次 操作的最大子段和,转移即可。
如果当前的 ,则可以从 进行转移,即选择把当前的 。之所以有 ,是为了排除 “考虑到第五个数,已经加了十次” 这种非法的状态转移。
如果 ,则可以从 进行,转移,即选择把当前的 。显然 需要大于 。
至于答案处理,在转移过程中如果 即可把 对 取 。含义是如果 后方能放开还没进行完的加法操作就尝试更新答案。
// Problem: Maximum Subarray
// Contest: Codeforces
// URL: https://m2.codeforces.com/contest/1796/problem/D
// Memory Limit: 512 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 200005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define int LL
#define LXF int
#define RIN read_32()
#define HH printf("\n")
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
using namespace std;
char buf[1<<20],*p1,*p2;
inline int read_32(){LXF X=0,w=0;char ch=0;while(ch<'0'||ch>'9'){w|=ch=='-';ch=GC;}while(ch>='0'&&ch<='9') X=(X<<3)+(X<<1)+(ch^48),ch=GC;return w?-X:X;}
int n,k,x;
int a[MAXN],dp[MAXN][25];
signed main(){
int T=RIN;
while(T--){
n=RIN,k=RIN,x=RIN;
foru(i,1,n){
a[i]=RIN;
}
for(int i=0;i<=n;i++){
for(int j=0;j<=k;j++){
dp[i][j]=-INF;
}
}
int ans=-INF;
for(int i=1;i<=n;i++){
for(int j=0;j<=min(k,i);j++){
if(i-1>=j)dp[i][j]=max(dp[i-1][j]+a[i]-x,a[i]-x);
if(j>0){
dp[i][j]=max(dp[i][j],max(dp[i-1][j-1]+a[i]+x,a[i]+x));
}
if(n-i>=k-j){
ans=max(ans,dp[i][j]);
}
}
}
printf("%lld\n",max(ans,0ll));
}
return 0;
}
T3 Explosions? (CF1795E)
简要题意:给定一个长度 的序列 ,你可以对每个数进行若干次 操作,使其变成一个严格单峰的序列,问操作数加上峰顶高度的最小值。
假设我们已经求出了 和 分别表示在不修改 的前提下,使得前 个数严格递增 / 后 个数严格递减的最小操作次数,那么显然:
又因为 和 是完全对称的,求完 之后把 翻转再跑一次就能得到 ,所以只需考虑怎么求 。
想一下朴素 暴力 DP,当前考虑到第 个数,那么如果 , 就需要修改。同理,如果 , 就需要修改。一旦我们找到一个不需要修改的 ,那么在这之前的修改我们就不用考虑了,直接使用 就可以,因为没有增高的操作,且降低 又会变劣,因此 不变,同时在 之前的数必须小于 ,于是这件事的代价就是 。
至于 和 之间的部分,他们本身的和由前缀和 得到,修改完后的和可以使用等差数列求和公式得到,于是修改 这部分代价的计算也是 的。注意 有可能大于 ,使得等差数列出现负项,这显然是错误的,因为我们最多减到 ,计算时把 对 取 即可。
设 , 为 的前缀和,则转移方程为:
复杂度瓶颈是 的查找。回看 需要满足的条件式:
发现 变得只和 有关,因此可以使用单调栈维护。
复杂度 。
// Problem: CF1795E Explosions?
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1795E
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 300005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define int LL
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
int n,h[MAXN];
int f[MAXN],g[MAXN];
int s[MAXN];
stack<LL> st;
signed main(){
int T=RIN;
while(T--){
n=RIN;
while(!st.empty()) st.pop();
foru(i,1,n){
h[i]=RIN;
s[i]=s[i-1]+h[i];
f[i]=g[i]=0;
}
st.push(1);
for(int i=2;i<=n;i++){
while(!st.empty() && h[st.top()]-st.top()>h[i]-i) st.pop();
int j;
if(st.empty()) j=0;
else j=st.top();
st.push(i);
f[i]+=f[j];
f[i]+=s[i]-s[j];
f[i]-=min(i-j,h[i])*(h[i]+h[i]-min(i-j,h[i])+1)/2;
}
reverse(h+1,h+1+n);
foru(i,1,n){
s[i]=s[i-1]+h[i];
}
while(!st.empty()) st.pop();
st.push(1);
for(int i=2;i<=n;i++){
while(!st.empty() && h[st.top()]-st.top()>h[i]-i) st.pop();
int j;
if(st.empty()) j=0;
else j=st.top();
st.push(i);
g[i]+=g[j];
g[i]+=s[i]-s[j];
g[i]-=min(i-j,h[i])*(h[i]+h[i]-min(i-j,h[i])+1)/2;
}
reverse(g+1,g+1+n);
reverse(h+1,h+1+n);
int ans=LLONG_MAX;
for(int i=1;i<=n;i++){
ans=min(ans,f[i]+g[i]+h[i]);
}
printf("%lld\n",ans);
}
return 0;
}
T4 Moscow Gorillas (CF1793D)
对于给出的两个长度为 的排列 和 ,请你算出有多少对 和 ( ) ,满足 。
其中,一段序列的 值表示 没有在该序列中出现的最小正整数。例如,$ \operatorname{MEX}([1, 3]) = 2 \operatorname{MEX}([5]) = 1 \operatorname{MEX}([3, 1, 2, 6]) = 4 $。
显然 的取值范围是 。记其为 ,考虑从小到大枚举 。
假设当前枚举到 ,则 的数均必须出现在 中,而 在 中不能出现,剩下的随意。
所以到 之后我们把 在两个排列里的下标加入 ,再查询最小下标和最大下标,分别记为 ,显然需满足:
在两个排列中会有两个下标,把较小的和较大的分别记作 ,则它们划分出了三个区间,即:
讨论,并对区间取交集,把左右端点区间长度相乘从而得到答案。 的情况特殊处理即可。
代码:
// Problem: CF1793D Moscow Gorillas
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1793D
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 200005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
int n;
int a[MAXN],at[MAXN];
int b[MAXN],bt[MAXN];
set<LL> s;
LL get(LL l,LL r){
if(r<l) return 0;
LL w=r-l+1;
return (1+w)*w/2;
}
signed main(){
n=RIN;
foru(i,1,n){
a[i]=RIN;
at[a[i]]=i;
}
foru(i,1,n){
b[i]=RIN;
bt[b[i]]=i;
}
LL ans=get(1,min(at[1],bt[1])-1)+get(min(at[1],bt[1])+1,max(at[1],bt[1])-1)+get(max(at[1],bt[1])+1,n)+1;
for(int M=2;M<=n;M++){
s.insert(at[M-1]);
s.insert(bt[M-1]);
LL lf=min(at[M],bt[M]);
LL rf=max(at[M],bt[M]);
LL Min=*s.begin();
LL Max=*s.rbegin();
if(Max<=lf-1){
ans+=(lf-Max)*Min;
}
if(lf+1<=Min && Max<=rf-1){
ans+=(rf-Max)*(Min-lf);
}
if(Min>=rf+1){
ans+=(Min-rf)*(n+1-Max);
}
}
cout<<ans;
return 0;
}
T5 Graph Coloring (CF1792F1)
题意:给定 ,请你对一个大小为 的完全图的边进行红蓝染色,先给出一个定义:
- 对于一个点集 ,称其为 “红联通” 当且仅当对于所有的 ,都存在一条路径 ,满足:“蓝联通” 的定义是与 “红联通” 对称的。
求出满足以下条件的染色方案数:
- 至少有一条红边
- 至少有一条蓝边
- 对于所有 的点集 ,它要么是红联通的,要么是蓝联通的,但不能同时满足二者。
考虑 DP。
注意区分下面的叙述细节,例如 “红联通” ”红边能联通“ 的含义不一样,请结合语境。
首先有一个性质,一个图与其补图必有一个是联通的。放在这个题里也是一样,红色部分和蓝色部分互为补图,所以必有一个联通了所有点。
还有一个性质,如果 是红联通的,那么它的子集也是红联通的。蓝联通同理。
由于红蓝染色之间存在双射,所以不妨钦定红色部分是那个联通的。也就是说红边联通了每一个点。
设 表示大小为 的,蓝色边没有联通所有点的图的染色方案数,那么显然 ,最后的答案等于原方案加翻转方案,再减去全红和全蓝方案,即 。
问题是怎么递推 。
对于大小为 的图,考虑所有包含 的点集 ,记其大小为 ,则 ,因为 号点已经确定,所以从 个点钟选择剩下 个点的方案数是 。
根据 的定义,如果想要让这个子集产生贡献,那么它必须是红联通的,所以 内边的染色方案是 。
记 为剩下 个点组成的集合,根据题目要求, 本身不能既是红联通又是蓝联通,但可以是红联通和蓝联通中的任意一个,因而染边方案数为 。注意如果 时要特判,不用乘 ,因为单个点既是红联通又是蓝联通。
考虑完了 和 边,在它们中间的边该如何染色呢?根据 定义,只有 “红边联通了所有点” 的染色方案才会被计入,我们已经保证了 内部的红边能联通 内部的所有点, 但不确定 内的红边能否联通 内的所有点,因为只有当 也是红联通的时候才可以,而 是有可能为蓝联通的。因此我们需要把跨 和 的所有边都染成红色,保证整个图符合 的定义,即红边能联通所有的点。
综上,对于枚举出来的 大小 ,其方案数为 。
所以转移方程:
如果问为什么 取不到 ,可以试试怎么用 来转移 。
代码:
// Problem: CF1792F1 Graph Coloring (easy version)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1792F1
// Memory Limit: 500 MB
// Time Limit: 5500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 100005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
const LL mod=998244353;
LL ksm(LL a,LL b){
a%=mod;
LL ret=1;
while(b){
if(b&1) ret=(ret*a)%mod;
b>>=1,a=(a*a)%mod;
}
return ret;
}
int n;
LL f[MAXN];
LL C[5005][5005];
signed main(){
n=RIN;
C[0][0]=1;
foru(i,1,n){
C[i][0]=1;
foru(j,1,i){
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
f[1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
f[i]=(f[i]+C[i-1][j-1]*f[j]%mod*f[i-j]%mod*(j==i-1?1:2)%mod)%mod;
}
}
cout<<(f[n]*2%mod-2+mod)%mod;
return 0;
}
T6 Divisors and Table (CF1792E)
题意:
给定一张 的表格和一个正整数 ,表格第 行第 列的数 。
现在需要你求出 的每个因子 是否在表格中出现,若出现,则求出其出现在表格中的最小行号。
其实不需要什么高深的技巧,直接分解 的所有因数, 的值域是 ,所以因数个数是 级别的。
先在 的因数里二分找到起点,之后暴力枚举,找到符合条件的数之后直接退出。
// Problem: CF1792E Divisors and Table
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1792E
// Memory Limit: 250 MB
// Time Limit: 2500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 100005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
vector<LL> s,s1,s2;
signed main(){
int T=RIN;
while(T--){
s.clear();
s1.clear();
s2.clear();
int n=RIN,a=RIN,b=RIN;
for(int i=1;i*i<=a;i++){
if(a%i==0){
s1.push_back(i);
s1.push_back(a/i);
}
}
for(int i=1;i*i<=b;i++){
if(b%i==0){
s2.push_back(i);
s2.push_back(b/i);
}
}
for(auto x:s1){
for(auto y:s2){
s.push_back((LL)x*(LL)y);
}
}
sort(s.begin(),s.end());
auto ptr=unique(s.begin(),s.end());
LL ans=0,cnt=0;
for(auto it=s.begin();it!=ptr;it++){
LL x=*it;
LL l=0,r=it-s.begin(),st;
while(l<=r){
LL mid=(l+r)>>1;
if(x/s[mid]<=n){
st=mid;
r=mid-1;
}else{
l=mid+1;
}
}
for(int i=st;i<s.size();i++){
if(s[i]>n) break;
if(x%s[i]==0 && x/s[i]<=n){
ans^=s[i];
cnt++;
break;
}
}
}
cout<<cnt<<' '<<ans<<endl;
}
return 0;
}
T7 Serval and Music Game (CF1789E)
题意:
给定整数 和长度为 的递增序列 。
定义 为满足下列要求的整数 的数量:
- 存在非负整数 使得 。
你需要求出 对 取模后的值。
从 枚举一个 :
如果 是 的因数,直接枚举倍数做,复杂度是调和级数的。
如果 不是 的因数,则原式:
设 ,则:
如果想要 ,则
显然 尽可能大, 尽可能小是不劣的。
于是把这个式子看做 “被除数=商*除数+余数" 的形式
则 对应商, 对应除数, 对应余数。
于是有:
设 ,那么在 已经敲定的情况下,所有符合要求的 满足 ,因此在值域上对 做前缀和就可以 查询在区间内的 数量。
所以我们来枚举 ,从 枚举到 。
之所以枚举到 是因为需要单独判断。当 的取值 时,意味着 ,而此时原式一定成立,因为余数 一定小于除数 ,而商 一定不小于除数 ,故 恒成立,所以比 大的 都可计入答案。
于是直接按照区间查贡献即可,加上整除分块的优化即可通过,原理就是 有可能与 相同,也就是 未发生变化,无需再次计算,特判掉即可。
// Problem: CF1789E Serval and Music Game
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1789E
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 1000005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
int a[MAXN];
int vis[10000003],cnt[10000003];
const LL mod=998244353;
signed main(){
int T=RIN;
for(int tm=1;tm<=T;tm++){
int n=RIN;
LL ans=0;
foru(i,1,n){
a[i]=RIN;
vis[a[i]]=tm;
}
foru(i,1,a[n]){
cnt[i]=cnt[i-1]+(vis[i]==tm);
}
LL last=0;
for(int x=1;x<=a[n];x++){
LL tot=0;
LL A=a[n]/x;
if(x!=1 && a[n]%x!=0 && a[n]%(x-1)!=0 && (LL)(a[n]/x)==(LL)(a[n]/(x-1))){
tot=last;
}else{
if(a[n]%x==0){
for(int i=1;(LL)i*A<=a[n];i++){
if(vis[i*A]==tm){
tot++;
}
}
}else{
for(int j=1;j<A && j*A<=a[n];j++){
tot=(tot+(cnt[min((LL)j*A+j,(LL)a[n])]-cnt[(LL)j*A-1])%mod)%mod;
}
if(A*A<=a[n]) tot=(tot+(cnt[a[n]]-cnt[A*A-1])%mod)%mod;
}
}
last=tot;
ans=(ans+(LL)tot*x%mod)%mod;
}
printf("%lld\n",ans);
}
return 0;
}
T8 Sum Over Zero (CF1788E)
题意:
给你一个长度为 的序列 ,现在请你找到这个序列的若干个不相交的合法子段。
对于你选择的一个子段 ,其为一个合法子段当且仅当 ,即子段的区间和非负。
对于一个合法的子段 ,我们记 ,且当子段 为空时,。
对于你选择的这若干个不相交合法子段,请最大化 并输出这个最大值。
DP 进行解决,设 表示决策了前 个数,最后一段以第 个数结尾的答案。转移时枚举上一段的最后一个元素,利用前缀和进行转移。状态转移方程:
复杂度 ,考虑从转移上下手进行优化。
对方程变形:
注意:因为有 的限制,因此有可能一次 都没有取过,这种情况下是不加 的,加以特判即可。
问题就是如何快速求出这个 。观察发现每一项都只和 有关,并且有两个 “限制”,一是 ,二是 ,所以我们选择在值域上加以修改,把 当做下标,这样 的限制就由值域上的前缀和解决了。
我们现在要做到单点修改,前缀查询最大值,因为 数组值域很大,所以可以使用权值线段树解决,复杂度 ,可以通过。
但继续观察,发现我们只关心 的相对大小,而不关心它的值,因此容易想到离散化。对 离散化后值域降到 ,可以使用普通线段树或者树状数组解决,减少了码量。
// Problem: CF1788E Sum Over Zero
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1788E
// Memory Limit: 250 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 200005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF LL
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
int n;
LL a[MAXN],dp[MAXN],s[MAXN],maxx[MAXN];
pair<LL,int> c[MAXN];
inline int lb(int x){return x&-x;}
class Tree{
public:
LL c[MAXN];
void add(int x,LL k){
for(;x<=n;x+=lb(x)){
c[x]=max(c[x],k);
}
}
LL ask(int x){
LL ret=LLONG_MIN;
for(;x;x-=lb(x)){
ret=max(ret,c[x]);
}
return ret;
}
}tr;
bool up[MAXN];
signed main(){
n=RIN;
LL Min=LLONG_MAX,top=LLONG_MIN;
foru(i,1,n){
tr.c[i]=LLONG_MIN;
a[i]=RIN;
c[i].first=s[i]=s[i-1]+a[i];
c[i].second=i;
if(s[i]>=0) up[i]=true;
top=max(top,s[i]);
Min=min(Min,s[i]);
}
sort(c+1,c+1+n);
foru(i,1,n){
s[c[i].second]=i;
}
maxx[1]=dp[1]=up[1];
for(int i=2;i<=n;i++){
if(up[i]){
dp[i]=i;
}else{
dp[i]=LLONG_MIN;
}
tr.add(s[i-1],maxx[i-1]-(i-1));
LL tmp=tr.ask(s[i]);
if(tmp!=LLONG_MIN){
dp[i]=max(dp[i],tmp+i);
}
maxx[i]=max(maxx[i-1],dp[i]);
}
cout<<maxx[n];
return 0;
}
T9 Different Arrays (CF1783D)
题意:
给你一个有 个元素的序列,你需要进行 次操作。
对于第 次操作,你可以选择让 且 或者可以选择让 且 。
问最后能产生多少个不同的序列。
容易想到只有这个 为 时会产生重复,因此直接记忆化搜索,传参传当前做到第 个操作,第 个数为 即可。
// Problem: CF1783D Different Arrays
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1783D
// Memory Limit: 500 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
const LL mod=998244353;
int n;
int a[305];
LL dp[305][200050];
LL f(int s,int x){
if(dp[s][x]) return dp[s][x];
if(s==n-1) return dp[s][x]=1;
if(x!=0) return dp[s][x]=(f(s+1,a[s+2]+x)+f(s+1,a[s+2]-x)+mod)%mod;
else return dp[s][x]=f(s+1,a[s+2]);
}
signed main(){
n=RIN;
foru(i,1,n){
a[i]=RIN;
}
cout<<f(1,a[2]);
return 0;
}
T10 Three Chairs (CF1780F)
题意:
给定一个数组,求满足以下条件的三元组个数:
首先对 排序,那么 和 的问题就可以解决。
问题变成求这个式子:
考虑如何处理整除的限制。由于 的值域很小,预处理时我们直接在值域上建桶,把 作为下标, 作为值扔进去。接着直接枚举 的倍数,check 一下有没有这个数,如果有就把下标扔进一个新的 。记最终 的大小为 ,则式子变成:
时间复杂度 。
// Problem: CF1780F Three Chairs
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1780F
// Memory Limit: 250 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 300005
#define eps 1e-9
#define foru(a,b,c) for(int a=b;a<=c;a++)
#define RT return 0;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
LXF x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*w;
}
vector<int> prim;
bitset<MAXN> vis;
int mu[MAXN];
void pre(){
vis[1]=mu[1]=1;
for(int i=2;i<=300000;i++){
if(!vis[i]){
prim.emplace_back(i);
mu[i]=-1;
}
for(int j=0;j<prim.size() && i*prim[j]<=300000;j++){
vis[i*prim[j]]=1;
if(i%prim[j]==0) break;
mu[i*prim[j]]=-mu[i];
}
}
}
int n;
LL a[MAXN],pos[MAXN],k,f[MAXN];
signed main(){
pre();
n=RIN;
foru(i,1,n){
a[i]=RIN;
}
sort(a+1,a+1+n);
foru(i,1,n){
f[a[i]]=i;
}
LL ans=0;
for(int d=1;d<=a[n];d++){
k=0;
for(int i=1;i*d<=a[n];i++){
if(f[i*d]){
pos[++k]=f[i*d];
}
}
LL tot=0;
tot-=k*(k-1)/2;
for(int i=2;i<=k;i++){
tot+=pos[i]*(i-1);
}
for(int i=1;i<k;i++){
tot-=pos[i]*(k-i);
}
ans+=mu[d]*tot;
}
cout<<ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步