前言
和学长学弟一起打的hdu多校,打的很菜没啥难题收录,因为难的我都不会做。
正题
hdu7152-Copy
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7152
题目大意
nnn个数字的序列aaa,mmm次操作,每次将一段[l,r][l,r][l,r]复制一份然后插入在这一段的后面,或者求某个位置的值。
li,ri≤nl_i,r_i\leq nli,ri≤n
解题思路
复制之后相当于让大于rir_iri的位置询问时都减去ri−li+1r_i-l_i+1ri−li+1这一段编号,也就是我们询问xxx时倒序枚举目前的询问,对于每个询问如果x>rix>r_ix>ri那么令x=x−ri−li+1x=x-r_i-l_i+1x=x−ri−li+1。
但是询问可能很多,我们考虑暴力重构,值保留最后q\sqrt qq个操作,然后询问时暴力倒序枚举。每到达q\sqrt qq个操作后我们就暴力重新构造一次序列。
重构的时候我们就一堆位置一起处理,每次倒序之后每次操作[li,ri][l_i,r_i][li,ri]相当于把[ri+1,ri+ri−li+1][r_i+1,r_i+r_i-l_i+1][ri+1,ri+ri−li+1]这个区间和[li,ri][l_i,r_i][li,ri]绑定,我们把这个区间删除,然后用并查集指向[li,ri][l_i,r_i][li,ri]。
然后删除的做法,我们可以标记一下删除,然后查询位置的时候用树状数组查询即可。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e5+10;
int T,n,q,k,ans,a[N],l[N],r[N],fa[N],t[N],f[N];
int find(int x)
{return (fa[x]==x)?(x):(fa[x]=find(fa[x]));}
void Change(int x,int val){while(x<=n+1){t[x]+=val;x+=lowbit(x);}return;
}
int Ask(int k){int x=0;k--;for(int i=17;i>=0;i--)if(x+(1<<i)<=n+1&&t[x+(1<<i)]<=k)x+=1<<i,k-=t[x];return x+1;
}
int main()
{scanf("%d",&T);while(T--){scanf("%d%d",&n,&q);k=ans=0;for(int i=1;i<=n;i++)scanf("%d",&a[i]);while(q--){int op;scanf("%d",&op);if(op==1){k++;scanf("%d%d",&l[k],&r[k]);if(k>=400){for(int i=1;i<=n+1;i++)t[i]=0,fa[i]=i;for(int i=1;i<=n+1;i++)Change(i,1);int m=n;for(int i=k;i>=1;i--){for(int j=0,p;j<=r[i]-l[i];j++){p=Ask(r[i]+1);if(p>n)break;fa[p]=Ask(l[i]+j);Change(p,-1);m--;}}for(int i=1;i<=m;i++)f[Ask(i)]=a[i];for(int i=1;i<=n;i++)a[i]=f[find(i)];k=0;}}else{int x;scanf("%d",&x);for(int i=k;i>=1;i--)if(x>r[i])x-=r[i]-l[i]+1;ans^=a[x];}}printf("%d\n",ans);}return 0;
}
hdu7184-Link is as bear
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7184
题目大意
给出nnn个数字的一个序列,你每次可以选择一个区间将其变为它们所有数的异或和,求将所有数字变成同一个数字,最大化这个数字。
保证有两个相同数字。
解题思路
考虑一个大于000的区间我们都可以将其变为000,并且000不影响操作,我们可以无视。
而如果有两个数字x,yx,yx,y之间相隔111(中间设为zzz)但是我们都要保留,那么此时我们可以用那两个相同的数字aaa,我们将它们变为x,z,yxora,yxorax,z,y\ xor\ a,y\ xor\ ax,z,y xor a,y xor a。
然后再异或一次左边两个得到x,zxoraxory,zxoraxory,yxorax,z\ xor\ a\ xor\ y,z\ xor\ a\ xor\ y,y\ xor\ ax,z xor a xor y,z xor a xor y,y xor a。
然后异或出x,zxoraxory,z,zx,z\ xor\ a\ xor\ y,z,zx,z xor a xor y,z,z,再异或得到x,axory,z,zx,a\ xor\ y,z,zx,a xor y,z,z,再用另一个aaa将xxx异或掉即可。
所以就算求最大异或和,线性基就行了。
hdu7190-BBQ
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7190
题目大意
一个字符串sss,每次可以插入/删除/修改一个字符,求至少操作多少次满足:
- sss的长度为444的倍数
- s4i+1=s4i+4s_{4i+1}=s_{4_i+4}s4i+1=s4i+4且s4i+2=s4i+3s_{4i+2}=s_{4_i+3}s4i+2=s4i+3
解题思路
我们考虑一下dpdpdp,看下能不能将复杂度压进26n26n26n以内。
一个一个字符考虑,然后dpdpdp。
- f0if0_if0i表示目前确定了第111个字符,为iii的方案。
- f1i,jf1_{i,j}f1i,j表示目前确定了前两个字符,分别是i,ji,ji,j的方案。
- f2if2_{i}f2i表示目前确定第三个字符,第一个是iii的方案。
- f4f4f4表示目前确定了四个字符的方案。
会发现瓶颈在f1i,jf1_{i,j}f1i,j的转移上,但是我们假设我们目前枚举到ppp,会发现每次只需要转移j=apj=a_pj=ap的方案,所以这部分转移也很好做,我们默认每个字符都会被删除,然后当一个字符不会被删除时会产生−1-1−1的贡献。
至于插入和修改,因为它们可以是任意字符,我们再开一个状态262626表示可以是任意字符就好了。
code
#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);
// n=1e6;for(int i=1;i<=n;i++)s[i]='a';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;
}
hdu7206-Planar graph
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7206
题目大意
给出一张平面图,求最少删掉多少条边使得其对偶图联通,输出字典序最小的方案。
解题思路
有环的话对偶图就不连通,所以删的只剩下一棵生成树就好了。
hdu7207-Find different
单独写了:https://blog.csdn.net/Mr_wuyongcong/article/details/126329092
hdu7217-Counting Good Arrays
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7217
题目大意
求有多少个长度不超过nnn且值域为[1,m][1,m][1,m]的序列满足每一个数都是下一个数的因数。
解题思路
我们反过来填,每个数字后面只能填它的倍数,那么如果第一个数是a1a_1a1,a2a_2a2就只有⌊ma1⌋\lfloor\frac{m}{a_1}\rfloor⌊a1m⌋个数字可以填。
我们考虑如果每次至少加一个因数,那么这个序列长度不超过logm\log mlogm,然后我们再用组合数把这些加因数的位置分配到nnn里面就行了。
设fi,jf_{i,j}fi,j表示⌊mal⌋=i\lfloor\frac{m}{a_l}\rfloor=i⌊alm⌋=i,且增加了jjj次因数时的答案,这样iii可以通过整除分块得到n\sqrt nn级别种取值,转移时也是整除分块去考虑加的因数就好了。
时间复杂度:O(m34logm)O(m^{\frac{3}{4}}\log m)O(m43logm)
有不带log\loglog的min25\text{min25}min25筛但是我不会。
code
#pragma GCC optimize(2)
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int N=1e5+10,P=1e9+7;
int cas,n,m,inv[70],a[N],p1[N],p2[N],f[N][30],c[60];
void Add(int &x,int y)
{x=(x+y>=P)?(x+y-P):(x+y);}
void Add(int &x,ll y)
{x=(x+y>=P)?(x+y-P):(x+y);}
int C(int n,int m){if(m>n)return 0;int ans=1;for(int i=0;i<m;i++)ans=1ll*ans*(n-i)%P;for(int i=1;i<=m;i++)ans=1ll*ans*inv[i]%P;return ans;
}
int main()
{inv[1]=1;for(int i=2;i<=60;i++)inv[i]=P-1ll*inv[P%i]*(P/i)%P;scanf("%d",&cas);while(cas--){scanf("%d%d",&m,&n);int lim=0,ans=0;while((1<<lim)<=n)lim++,c[lim]=C(m,lim);int T=sqrt(n),cnt=0;for(int l=1,r;l<=n;l=r+1){r=n/(n/l);a[++cnt]=n/l;if(n/l<=T)p1[n/l]=cnt;else p2[n/(n/l)]=cnt;f[cnt][0]=r-l+1;for(int i=1;i<lim;i++)f[cnt][i]=0;}f[1][0]=1;for(int i=1;i<=cnt;i++){int x=a[i],y;for(int l=2,r;l<=x;l=r+1){r=x/(x/l);if(x/l<=T)y=p1[x/l];else y=p2[n/(x/l)];for(int k=0;k<lim-1;k++)Add(f[y][k+1],1ll*f[i][k]*(r-l+1)%P);}for(int j=0;j<lim;j++)Add(ans,1ll*c[j+1]*f[i][j]%P);}printf("%d\n",ans);}return 0;
}
hdu7221-Darkmoon Faire
http://acm.hdu.edu.cn/showproblem.php?pid=7221
题目大意
有一个长度为nnn的序列,求将它分割成若干连续段的方案满足:
- 将每一段单独取出来后,最大值在奇数位置,最小值在偶数位置。
解题思路
一个朴素的想法是我们去搞两个单调栈去维护最小最大值,然后以这两个单调栈里的数去分割成若干个区间,每个区间产生贡献的方式不同。
但是我们发现一个单调栈改变时可能对应另一个单调栈里的多个区间,所以不能暴力修改,我们用线段树维护每个单调栈里(不考虑令一个单调栈)产生贡献的数字奇数和偶数位置的和,然后修改时查询即可。
code
#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;
}
hdu7224-Ironforge
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7224
题目大意
一个nnn段的数轴,第iii个点上有数字aia_iai,通过(i,i+1)(i,i+1)(i,i+1)要求存在之前经过的某个axa_xax满足bi∣axb_i|a_xbi∣ax。
qqq次询问xxx能否到达yyy。
解题思路
一个点能到达的点肯定是一个区间,暴力的想法是左右两边扩展。实际上暴力加上一个记忆化就能过。
我们假设xxx能到达yyy,x+1x+1x+1能到达yyy但是x+1x+1x+1不能到达xxx,那么此时x+1∼yx+1\sim yx+1∼y这段路上bib_ibi的因数x+1x+1x+1和xxx都有,但是bxb_xbx这个因数x+1x+1x+1没有xxx有,也就是xxx比x+1x+1x+1多一个因数。
所以这种情况不会往左出现很多,因为xxx需要不停增大,所以我们从左往右暴力,一个点暴力时如果能到达它左边的点就让它能到达的区间并上左边那个点能到达的区间即可。
时间复杂度:O((q+n)logn)O((q+n)\log n)O((q+n)logn)
hdu7226-Darnassus
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7226
题目大意
有一个排列ppp,xxx和yyy之间的边权权值为∣x−y∣×∣px−py∣|x-y|\times |p_x-p_y|∣x−y∣×∣px−py∣,求最小生成树。
解题思路
因为ppp是排列,会发现合并两个连通块的代价不超过n−1n-1n−1(连接最近的两个)。
所以我们直接删除代价大于n−1n-1n−1的边即可,那么剩下的对数满足min{∣i−j∣,∣pi−pj∣}≤n\min\{|i-j|,|p_i-p_j|\}\leq \sqrt nmin{∣i−j∣,∣pi−pj∣}≤n,所以暴力枚举n\sqrt nn以内的对然后记下每一条合法的边跑最小生成树。
因为边权不超过nnn所以可以直接桶排。
时间复杂度:O(nnα(n))O(n\sqrt n\alpha(n))O(nnα(n))
hdu7232-Shattrath City
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7232
题目大意
求有多少个长度为mmm,值域为nnn的序列满足每一个长度为nnn的连续段都不是一个1∼n1\sim n1∼n的排列。
解题思路
考虑容斥,钦定一些区间是1∼n1\sim n1∼n的排列,那么如果相邻位置x,yx,yx,y两个不超过nnn贡献是(y−x)!(y-x)!(y−x)!,否则是n!×ny−xn!\times n^{y-x}n!×ny−x。
分治NTTNTTNTT或者多项式求逆计算即可。