2024 提高组杂题
2024 提高组杂题
T1 [CF1763C] Another Array Problem
弱智题。容易发现无论怎么操作
constexpr int MAXN=2e5+5;
int T,n;
long long a[MAXN];
int main(){
T=read();
while(T--){
n=read();
for(int i=1;i<=n;++i) a[i]=read();
if(n==2) write(max(abs(a[2]-a[1])<<1,a[1]+a[2]));
else if(n==3) write(max(a[1]+a[2]+a[3],max({a[1],a[3],abs(a[1]-a[2]),abs(a[2]-a[3])})*n));
else write(*max_element(a+1,a+n+1)*n);
}
return fw,0;
}
T2 [CF1775E] The Human Equation
精妙构造题。题目所给的操作看似复杂,但观察到一个加一、一个减一这种操作很像差分,于是把
显然最少操作次数为
constexpr int MAXN=2e5+5;
int T,n;
long long a[MAXN];
int main(){
T=read();
while(T--){
n=read();
for(int i=1;i<=n;++i) a[i]=a[i-1]+read();
long long mx=0,mn=0;
for(int i=1;i<=n;++i) mx=max(mx,a[i]),mn=min(mn,a[i]);
write(mx-mn);
}
return fw,0;
}
T3 [CF1290C] Prefix Enlightenment
前置知识:种类并查集。代表例题:[NOI2001] 食物链。
首先需要理解 ”任意三个子集的交集为空集“ 是什么意思。翻译成人话就是:任意一点顶多出现在两个集合中。于是我们设
题目要求求出对于所有
,则 能且仅能操作一个; ,则 要么都操作,要么都不操作。
这种维护条件的连通性的题目,想到种类并查集。对于每个集合
,则连边 和 ; ,则连边 和 。
显然,每一条边都代表一种合法的方案。连完边之后,对于每一个连通块,要么选 ”操作“,要么选 ”不操作“,于是我们在合并的时候统计第一层和第二层的最小值即可。
需要注意:
- 注意不能按秩合并,那样会导致关系混乱;只能用路径压缩。
- 还要注意:要处理一个点仅被一个集合包含的情况。在上文我们已经说了,如果不存在就是
。所以 点需要用来处理这种情况。并查集的空间应该是 的。 - 对于这个
点不能操作。所以对于 点的两层情况需要特判,只能取不包含的那一部分。
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=3e5+5;
int n,k,ans;
char s[MAXN];
int L[MAXN],R[MAXN];
int f[MAXN<<1],siz[MAXN<<1][2];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void combine(int u,int v){
int fu=find(u),fv=find(v);
if(fu==fv) return;
ans-=min(siz[fu][0],siz[fu][1])+min(siz[fv][0],siz[fv][1]);
siz[fu][0]+=siz[fv][0];
siz[fu][1]+=siz[fv][1];
f[fv]=fu;
ans+=min(siz[fu][0],siz[fu][1]);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>k>>(s+1);
for(int i=1,c,x;i<=k;++i){
cin>>c;
for(int j=1;j<=c;++j){
cin>>x;
L[x]?R[x]=i:L[x]=i;
}
}
for(int i=0;i<=k;++i) f[i]=i,siz[i][0]=1;
for(int i=k+1;i<=(k<<1|1);++i) f[i]=i,siz[i][1]=1;
for(int i=1,rt;i<=n;++i){
if(s[i]=='1') combine(L[i],R[i]),combine(L[i]+k+1,R[i]+k+1);
else combine(L[i]+k+1,R[i]),combine(L[i],R[i]+k+1);
rt=find(0);
if(siz[rt][0]<siz[rt][1]) cout<<(ans>>1)+siz[rt][1]-siz[rt][0]<<'\n';
else cout<<(ans>>1)<<'\n';
}
return 0;
}
T4 [CF1458D] Flip and Reverse
想象力/人类智慧/脑电波的构造题。对原串记
然后转化题目中的奇怪操作。
- 要求
的 01 个数相等,则 ; - 翻转 + 反转,实际上等于将
的这些边反向。
所以该操作就等价于选择一个环然后将环上所有边反向。
然后需要观察出一个性质:操作前后,原图包含的边集不变。因为操作的是一个环,在操作前
还需要观察出另外一个性质。。原图任意一条欧拉回路代表的字符串都可以由原串经过操作得到。具体可以看 @tzc_wk 的证明。
于是此题就变为:求字典序最小的欧拉序!
直接贪心即可。
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=5e5+5,N=5e5;
int T,n,cnt[MAXN<<1][2];
string s;
int main(){
ios::sync_with_stdio(0);
cin.tie(nullptr),cout.tie(nullptr);
cin>>T;
while(T--){
cin>>s;
n=s.size();
s=' '+s;
for(int i=1,cur=0;i<=n;++i){
++cnt[cur+N][s[i]-'0'];
cur+=s[i]=='0'?-1:1;
}
for(int i=1,x=0;i<=n;++i)
if(cnt[x+N][0]&&cnt[x-1+N][1])
--cnt[x+N][0],--x,cout<<0;
else if(cnt[x+N][1])
--cnt[x+N][1],++x,cout<<1;
else --cnt[x+N][0],--x,cout<<0;
cout<<'\n';
}
return 0;
}
T5 [CF1389F] Bicolored Segments
线段树优化 DP,当然也可以用神秘的 multiset 做。
首先像我就想到二分答案 + 离散化上去了,不过想一想就知道会假。
不过离散化肯定是正确的,先把所有区间离散化。
正解设
不妨设当前线段是黑色的,也就是
如果不选当前线段,显然有:
如果选当前线段,设上一条被选择的白色线段的右端点是
这里想要优化就需要状态提前计算。事先将所有
constexpr int MAXN=2e5+5;
int n,mx,b[MAXN<<1],tot;
struct SEG{
int l,r,t;
bool operator<(const SEG&x)const{
return r<x.r;
}
}a[MAXN];
int max(int a,int b){
return a>b?a:b;
}
struct{
#define lp p<<1
#define rp p<<1|1
#define mid ((s+t)>>1)
struct SegTree{
int c,lazy;
}st[MAXN<<3];
void pushdown(int p){
if(!st[p].lazy) return;
st[lp].c+=st[p].lazy,st[lp].lazy+=st[p].lazy;
st[rp].c+=st[p].lazy,st[rp].lazy+=st[p].lazy;
st[p].lazy=0;
}
void pushup(int p){
st[p].c=max(st[lp].c,st[rp].c);
}
void mdf(int l,int r,int k,int s=0,int t=mx,int p=1){
if(l<=s&&t<=r) return st[p].c+=k,st[p].lazy+=k,void();
pushdown(p);
if(l<=mid) mdf(l,r,k,s,mid,lp);
if(mid<r) mdf(l,r,k,mid+1,t,rp);
pushup(p);
}
void chg(int x,int k,int s=0,int t=mx,int p=1){
if(s==t) return st[p].c=k,void();
pushdown(p);
if(x<=mid) chg(x,k,s,mid,lp);
else chg(x,k,mid+1,t,rp);
pushup(p);
}
int sum(int l,int r,int s=0,int t=mx,int p=1){
if(l<=s&&t<=r) return st[p].c;
pushdown(p);
int res=0;
if(l<=mid) res=max(res,sum(l,r,s,mid,lp));
if(mid<r) res=max(res,sum(l,r,mid+1,t,rp));
return res;
}
}s[2];
int main(){
n=read();
for(int i=1;i<=n;++i){
a[i]={read(),read(),read()-1};
b[++tot]=a[i].l,b[++tot]=a[i].r;
}
sort(b+1,b+tot+1);
tot=unique(b+1,b+tot+1)-b-1;
for(int i=1;i<=n;++i){
a[i].l=lower_bound(b+1,b+tot+1,a[i].l)-b;
a[i].r=lower_bound(b+1,b+tot+1,a[i].r)-b;
mx=max(mx,a[i].r);
}
sort(a+1,a+n+1);
for(int i=1,p;i<=n;++i){
p=a[i].t;
s[p].mdf(0,a[i].l-1,1);
s[!p].chg(a[i].r,max(s[p].sum(0,a[i].l-1),s[!p].sum(0,a[i].r)));
}
printf("%d\n",max(s[0].st[1].c,s[1].st[1].c));
return 0;
}
T6 [CF1580D] Subsequence
笛卡尔树的构造。将原式化为:
把所有的
设
constexpr int MAXN=4005;
int n,m,a[MAXN];
int ls[MAXN],rs[MAXN],stk[MAXN],top;
int f[MAXN][MAXN],tmp[MAXN],siz[MAXN];
void insert(int k,int w){
while(top&&w<a[stk[top]]) ls[k]=stk[top--];
if(top) rs[stk[top]]=k;
stk[++top]=k;
}
bool vis[MAXN];
void dfs(int u);
void work(int u,int v){
dfs(v);
for(int i=0;i<=siz[u];++i) tmp[i]=f[u][i],f[u][i]=0;
for(int i=0;i<=siz[u];++i)
for(int j=0;j<=siz[v];++j)
f[u][i+j]=max(f[u][i+j],tmp[i]+f[v][j]-2*a[u]*i*j);
siz[u]+=siz[v];
}
void dfs(int u){
f[u][1]=(m-1)*a[u];
siz[u]=1;
if(ls[u]) work(u,ls[u]);
if(rs[u]) work(u,rs[u]);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;++i) insert(i,a[i]=read());
for(int i=1;i<=n;++i) vis[ls[i]]=vis[rs[i]]=1;
for(int i=1;i<=n;++i)
if(!vis[i]){
dfs(i);
printf("%lld\n",f[i][m]);
return 0;
}
}
T7 [CF1720D2] Xor-Subsequence (hard version)
Trie 树优化 DP。发现如果
设
移项得
对于第
- 若
,则 , ; - 若
,则 , 。
我们发现它们得到答案一定是两个数位的值不同,至此我们转化为了只需要一个参数的问题。
我们在学习 Trie 的时候就知道,异或运算由于其特性,可以转到 Trie 树上维护最大值。刚才我们得到了
于是我们把每个
constexpr int MAXN=3e5+5;
int T,n,a[MAXN],ans;
int f[MAXN],trie[MAXN<<5][2],mx[MAXN<<5][2],tot=1;
void insert(int x,int val){
int now=1,p=a[x]^(x-1);
for(int j=30;~j;--j){
bool bp=p>>j&1,bx=(x-1)>>j&1;
if(!trie[now][bp]) trie[now][bp]=++tot;
mx[trie[now][bp]][bx]=max(mx[trie[now][bp]][bx],val);
now=trie[now][bp];
}
}
int query(int x){
int now=1,p=a[x]^(x-1),res=0;
for(int j=30;~j;--j){
bool bp=p>>j&1,bx=a[x]>>j&1;
if(trie[now][!bp]) res=max(res,mx[trie[now][!bp]][!bx]);
if(trie[now][bp]) now=trie[now][bp];
else break;
}
return res;
}
int main(){
T=read();
while(T--){
n=read();
ans=0;
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i){
f[i]=query(i)+1;
insert(i,f[i]);
ans=max(ans,f[i]);
}
write(ans);
for(int i=1;i<=tot;++i) trie[i][0]=trie[i][1]=mx[i][0]=mx[i][1]=0;
tot=1;
}
return fw,0;
}
T8 [CF1876F] Indefinite Clownfish
一道真正的黑题。逃。
T9 [CF193D] Two Segments
线段树维护值域区间。
一般对于这种 “公差为
对于这种区间问题,一般来说是一个一个 “加入”,然后统计答案。关键在于,我们需要维护每个加入点对总区间数的影响。如果加入的这个
- 左右两边的数都比
小,也就是它们原本都是在 之内的,这时 的加入相当于连接了原本的两个区间,会使得区间个数 ; - 左右两边的数一个比
大、一个比 小,相当于扩展了原区间,则区间个数不变; - 都比
大,相当于用原本的一个区间一次扩展了两个区间,区间个数 。
具体实现上来说,我们按照
为什么是
以上,就是我对这道题、对线段树维护区间的浅薄理解。
#define lp p<<1
#define rp p<<1|1
using ll=long long;
constexpr int MAXN=3e5+5;
int n,a[MAXN],p[MAXN];
ll ans;
struct{
ll mn,lazy,tm,ctm;
}st[MAXN<<2];
ll min(ll a,ll b){
return a<b?a:b;
}
void pushup(int p){
st[p].mn=min(st[lp].mn,st[rp].mn);
st[p].tm=st[lp].tm*(st[lp].mn==st[p].mn)+st[rp].tm*(st[rp].mn==st[p].mn);
st[p].ctm=st[lp].ctm*(st[lp].mn==st[p].mn)+st[lp].tm*(st[lp].mn==st[p].mn+1)+st[rp].ctm*(st[rp].mn==st[p].mn)+st[rp].tm*(st[rp].mn==st[p].mn+1);
}
void build(int s,int t,int p){
if(s==t) return st[p].tm=1,void();
int mid=(s+t)>>1;
build(s,mid,lp),build(mid+1,t,rp);
pushup(p);
}
void pushdown(int p){
if(!st[p].lazy) return;
st[lp].mn+=st[p].lazy;
st[lp].lazy+=st[p].lazy;
st[rp].mn+=st[p].lazy;
st[rp].lazy+=st[p].lazy;
st[p].lazy=0;
}
void mdf(int l,int r,ll k,int s=1,int t=n,int p=1){
if(l>t||s>r) return;
if(l<=s&&t<=r) return st[p].mn+=k,st[p].lazy+=k,void();
pushdown(p);
int mid=(s+t)>>1;
mdf(l,r,k,s,mid,lp),mdf(l,r,k,mid+1,t,rp);
pushup(p);
}
int sum(int l,int r,int s=1,int t=n,int p=1){
if(l>t||s>r) return 0;
if(l<=s&&t<=r) return st[p].tm*(st[p].mn<=2)+st[p].ctm*(st[p].mn<=1);
pushdown(p);
int mid=(s+t)>>1;
return sum(l,r,s,mid,lp)+sum(l,r,mid+1,t,rp);
}
int main(){
n=read();
for(int i=1;i<=n;++i) a[i]=read(),p[a[i]]=i;
build(1,n,1);
for(int i=1;i<=n;++i){
mdf(1,i,1);
if(a[p[i]-1]<i&&a[p[i]-1]) mdf(1,a[p[i]-1],-1);
if(a[p[i]+1]<i&&a[p[i]+1]) mdf(1,a[p[i]+1],-1);
ans+=sum(1,i-1);
}
printf("%lld\n",ans);
return 0;
}
T10 [AGC056C] 01 Balanced
竟然是一道差分约束题。逼我补差分约束是吧。
看到要满足
又因为
同时,差分约束还能很好地满足 “最小化原串字典序” 的需要。因为原串字典序最小,所以原串的
具体而言,建所有
constexpr int MAXN=1e6+5;
int n,m,head[MAXN],tot,dis[MAXN];
struct{
int v,to,w;
}e[MAXN<<2];
void addedge(int u,int v,int w){
e[++tot]={v,head[u],w};
head[u]=tot;
}
void bfs(){
memset(dis,-1,sizeof(int)*(n+5));
dis[0]=0;
deque<int>q;
q.emplace_back(0);
while(!q.empty()){
int u=q.front();
q.pop_front();
for(int i=head[u];i;i=e[i].to){
if(~dis[e[i].v]) continue;
dis[e[i].v]=dis[u]+e[i].w;
e[i].w?q.emplace_back(e[i].v):q.emplace_front(e[i].v);
}
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i) addedge(i,i-1,1),addedge(i-1,i,1);
for(int i=1,l,r;i<=m;++i){
l=read(),r=read();
addedge(l-1,r,0),addedge(r,l-1,0);
}
bfs();
for(int i=1;i<=n;++i) write(dis[i]-dis[i-1]<0,'#');
return putchar('\n'),fw,0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具