和学长学弟一起打的hdu多校,打的很菜没啥难题收录,因为难的我都不会做。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7152
n个数字的序列a,m次操作,每次将一段[l,r]复制一份然后插入在这一段的后面,或者求某个位置的值。
li,ri≤n
复制之后相当于让大于ri的位置询问时都减去ri−li+1这一段编号,也就是我们询问x时倒序枚举目前的询问,对于每个询问如果x>ri那么令x=x−ri−li+1。
但是询问可能很多,我们考虑暴力重构,值保留最后√q个操作,然后询问时暴力倒序枚举。每到达√q个操作后我们就暴力重新构造一次序列。
重构的时候我们就一堆位置一起处理,每次倒序之后每次操作[li,ri]相当于把[ri+1,ri+ri−li+1]这个区间和[li,ri]绑定,我们把这个区间删除,然后用并查集指向[li,ri]。
然后删除的做法,我们可以标记一下删除,然后查询位置的时候用树状数组查询即可。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7184
给出n个数字的一个序列,你每次可以选择一个区间将其变为它们所有数的异或和,求将所有数字变成同一个数字,最大化这个数字。
保证有两个相同数字。
考虑一个大于0的区间我们都可以将其变为0,并且0不影响操作,我们可以无视。
而如果有两个数字x,y之间相隔1(中间设为z)但是我们都要保留,那么此时我们可以用那两个相同的数字a,我们将它们变为x,z,y xor a,y xor a。
然后再异或一次左边两个得到x,z xor a xor y,z xor a xor y,y xor a。
然后异或出x,z xor a xor y,z,z,再异或得到x,a xor y,z,z,再用另一个a将x异或掉即可。
所以就算求最大异或和,线性基就行了。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7190
一个字符串s,每次可以插入/删除/修改一个字符,求至少操作多少次满足:
- s的长度为4的倍数
- s4i+1=s4i+4且s4i+2=s4i+3
我们考虑一下dp,看下能不能将复杂度压进26n以内。
一个一个字符考虑,然后dp。
- f0i表示目前确定了第1个字符,为i的方案。
- f1i,j表示目前确定了前两个字符,分别是i,j的方案。
- f2i表示目前确定第三个字符,第一个是i的方案。
- f4表示目前确定了四个字符的方案。
会发现瓶颈在f1i,j的转移上,但是我们假设我们目前枚举到p,会发现每次只需要转移j=ap的方案,所以这部分转移也很好做,我们默认每个字符都会被删除,然后当一个字符不会被删除时会产生−1的贡献。
至于插入和修改,因为它们可以是任意字符,我们再开一个状态26表示可以是任意字符就好了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=27;
int T,n,f0[N],f1[N][N],f2[N],g[N],f3;
char s[1100000];
int Min(int x,int y)
{return (x<y)?x:y;}
int main()
{
scanf("%d",&T);
while(T--){
scanf("%s",s+1);
n=strlen(s+1);
int mi=0;
memset(f0,0x3f,sizeof(f0));
memset(f1,0x3f,sizeof(f1));
memset(f2,0x3f,sizeof(f2));
memset(g,0x3f,sizeof(g));
f3=0;
f0[26]=Min(f0[26],f3+1);
for(int j=0;j<27;j++)f1[j][26]=Min(f1[j][26],f0[j]+1),g[j]=Min(g[j],f1[j][26]);
for(int j=0;j<27;j++)f2[j]=Min(f2[j],g[j]+1);
for(int j=0;j<27;j++)f3=Min(f3,f2[j]+1);
for(int i=1;i<=n;i++){
f0[26]=Min(f0[26],f3+1);
for(int j=0;j<27;j++)f1[j][26]=Min(f1[j][26],f0[j]+1),g[j]=Min(g[j],f1[j][26]);
for(int j=0;j<27;j++)f2[j]=Min(f2[j],g[j]+1);
for(int j=0;j<27;j++)f3=Min(f3,f2[j]+1);
int c=s[i]-'a',p3=f3;
f3=Min(f3,Min(f2[c],f2[26])-1);
for(int j=0;j<26;j++)f3=Min(f3,f2[j]);
for(int j=0;j<27;j++){
f2[j]=Min(f2[j],Min(f1[j][c],f1[j][26])-1);
f2[j]=Min(f2[j],g[j]);
}
for(int j=0;j<27;j++){
f1[j][c]=Min(f1[j][c],f0[j]-1),g[j]=Min(g[j],f1[j][c]);
f1[j][26]=Min(f1[j][26],f0[j]),g[j]=Min(g[j],f1[j][26]);
}
f0[c]=Min(f0[c],p3-1);
f0[26]=Min(f0[26],Min(p3,f3+1));
for(int j=0;j<27;j++)f1[j][26]=Min(f1[j][26],f0[j]+1),g[j]=Min(g[j],f1[j][26]);
for(int j=0;j<27;j++)f2[j]=Min(f2[j],g[j]+1);
for(int j=0;j<27;j++)f3=Min(f3,f2[j]+1);
}
printf("%d\n",f3+n);
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7206
给出一张平面图,求最少删掉多少条边使得其对偶图联通,输出字典序最小的方案。
有环的话对偶图就不连通,所以删的只剩下一棵生成树就好了。
单独写了:https://blog.csdn.net/Mr_wuyongcong/article/details/126329092
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7217
求有多少个长度不超过n且值域为[1,m]的序列满足每一个数都是下一个数的因数。
我们反过来填,每个数字后面只能填它的倍数,那么如果第一个数是a1,a2就只有⌊ma1⌋个数字可以填。
我们考虑如果每次至少加一个因数,那么这个序列长度不超过logm,然后我们再用组合数把这些加因数的位置分配到n里面就行了。
设fi,j表示⌊mal⌋=i,且增加了j次因数时的答案,这样i可以通过整除分块得到√n级别种取值,转移时也是整除分块去考虑加的因数就好了。
时间复杂度:O(m34logm)
有不带log的min25筛但是我不会。
http://acm.hdu.edu.cn/showproblem.php?pid=7221
有一个长度为n的序列,求将它分割成若干连续段的方案满足:
- 将每一段单独取出来后,最大值在奇数位置,最小值在偶数位置。
一个朴素的想法是我们去搞两个单调栈去维护最小最大值,然后以这两个单调栈里的数去分割成若干个区间,每个区间产生贡献的方式不同。
但是我们发现一个单调栈改变时可能对应另一个单调栈里的多个区间,所以不能暴力修改,我们用线段树维护每个单调栈里(不考虑令一个单调栈)产生贡献的数字奇数和偶数位置的和,然后修改时查询即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#define ll long long
using namespace std;
const ll N=3e5+10,P=998244353;
ll cas,n,a[N];stack<int> s,t;
struct SegTree{
ll w[N<<2][2][2],lazy[N<<2];
void Clear(){
memset(w,0,sizeof(w));
memset(lazy,0,sizeof(lazy));
}
void Downdata(ll x){
if(!lazy[x])return;
swap(w[x*2][0],w[x*2][1]);
swap(w[x*2+1][0],w[x*2+1][1]);
lazy[x*2]^=1;lazy[x*2+1]^=1;lazy[x]=0;return;
}
void Ins(ll x,ll L,ll R,ll pos,ll val){
if(L==R){w[x][0][pos&1]=val;return;}
ll mid=(L+R)>>1;Downdata(x);
if(pos<=mid)Ins(x*2,L,mid,pos,val);
else Ins(x*2+1,mid+1,R,pos,val);
w[x][0][0]=(w[x*2][0][0]+w[x*2+1][0][0])%P;
w[x][1][0]=(w[x*2][1][0]+w[x*2+1][1][0])%P;
w[x][0][1]=(w[x*2][0][1]+w[x*2+1][0][1])%P;
w[x][1][1]=(w[x*2][1][1]+w[x*2+1][1][1])%P;
}
void Change(ll x,ll L,ll R,ll l,ll r){
if(L==l&&R==r){lazy[x]^=1;swap(w[x][0],w[x][1]);return;}
ll mid=(L+R)>>1;Downdata(x);
if(r<=mid)Change(x*2,L,mid,l,r);
else if(l>mid)Change(x*2+1,mid+1,R,l,r);
else Change(x*2,L,mid,l,mid),Change(x*2+1,mid+1,R,mid+1,r);
w[x][0][0]=(w[x*2][0][0]+w[x*2+1][0][0])%P;
w[x][1][0]=(w[x*2][1][0]+w[x*2+1][1][0])%P;
w[x][0][1]=(w[x*2][0][1]+w[x*2+1][0][1])%P;
w[x][1][1]=(w[x*2][1][1]+w[x*2+1][1][1])%P;
}
ll Ask(ll x,ll L,ll R,ll l,ll r,ll k){
if(L==l&&R==r)return w[x][1][k];
ll mid=(L+R)>>1;Downdata(x);
if(r<=mid)return Ask(x*2,L,mid,l,r,k);
if(l>mid)return Ask(x*2+1,mid+1,R,l,r,k);
return (Ask(x*2,L,mid,l,mid,k)+Ask(x*2+1,mid+1,R,mid+1,r,k))%P;
}
}T[2];
signed main()
{
scanf("%lld",&cas);
while(cas--){
while(!s.empty())s.pop();T[0].Clear();
while(!t.empty())t.pop();T[1].Clear();
scanf("%lld",&n);
ll sum=0;s.push(0);t.push(0);
T[0].Ins(1,1,n,1,1);T[1].Ins(1,1,n,1,1);T[1].Change(1,1,n,1,1);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
while(s.size()>1&&a[i]<a[s.top()]){
ll x=s.top();s.pop();
(sum-=T[1].Ask(1,1,n,s.top()+1,x,!(x&1)))%=P;
if((x-i)&1)T[0].Change(1,1,n,s.top()+1,x);
(sum+=T[1].Ask(1,1,n,s.top()+1,x,!(i&1)))%=P;
}
while(t.size()>1&&a[i]>a[t.top()]){
ll x=t.top();t.pop();
(sum-=T[0].Ask(1,1,n,t.top()+1,x,x&1))%=P;
if((x-i)&1)T[1].Change(1,1,n,t.top()+1,x);
(sum+=T[0].Ask(1,1,n,t.top()+1,x,i&1))%=P;
}
if(i<n){
T[0].Ins(1,1,n,i+1,sum);
T[1].Ins(1,1,n,i+1,sum);
T[1].Change(1,1,n,i+1,i+1);
}
s.push(i);t.push(i);
}
printf("%lld\n",(sum+P)%P);
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7224
一个n段的数轴,第i个点上有数字ai,通过(i,i+1)要求存在之前经过的某个ax满足bi|ax。
q次询问x能否到达y。
一个点能到达的点肯定是一个区间,暴力的想法是左右两边扩展。实际上暴力加上一个记忆化就能过。
我们假设x能到达y,x+1能到达y但是x+1不能到达x,那么此时x+1∼y这段路上bi的因数x+1和x都有,但是bx这个因数x+1没有x有,也就是x比x+1多一个因数。
所以这种情况不会往左出现很多,因为x需要不停增大,所以我们从左往右暴力,一个点暴力时如果能到达它左边的点就让它能到达的区间并上左边那个点能到达的区间即可。
时间复杂度:O((q+n)logn)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7226
有一个排列p,x和y之间的边权权值为|x−y|×|px−py|,求最小生成树。
因为p是排列,会发现合并两个连通块的代价不超过n−1(连接最近的两个)。
所以我们直接删除代价大于n−1的边即可,那么剩下的对数满足min{|i−j|,|pi−pj|}≤√n,所以暴力枚举√n以内的对然后记下每一条合法的边跑最小生成树。
因为边权不超过n所以可以直接桶排。
时间复杂度:O(n√nα(n))
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7232
求有多少个长度为m,值域为n的序列满足每一个长度为n的连续段都不是一个1∼n的排列。
考虑容斥,钦定一些区间是1∼n的排列,那么如果相邻位置x,y两个不超过n贡献是(y−x)!,否则是n!×ny−x。
分治NTT或者多项式求逆计算即可。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2021-08-14 Loj#2880-「JOISC 2014 Day3」稻草人【CDQ分治,单调栈,二分】