和学长学弟一起打的hdu多校,打的很菜没啥难题收录,因为难的我都不会做。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7152
n n n个数字的序列 a a a, m m m次操作,每次将一段 [ l , r ] [l,r] [l,r]复制一份然后插入在这一段的后面,或者求某个位置的值。
l i , r i ≤ n l_i,r_i\leq n li,ri≤n
复制之后相当于让大于 r i r_i ri的位置询问时都减去 r i − l i + 1 r_i-l_i+1 ri−li+1这一段编号,也就是我们询问 x x x时倒序枚举目前的询问,对于每个询问如果 x > r i x>r_i x>ri那么令 x = x − r i − l i + 1 x=x-r_i-l_i+1 x=x−ri−li+1。
但是询问可能很多,我们考虑暴力重构,值保留最后 q \sqrt q q个操作,然后询问时暴力倒序枚举。每到达 q \sqrt q q个操作后我们就暴力重新构造一次序列。
重构的时候我们就一堆位置一起处理,每次倒序之后每次操作 [ l i , r i ] [l_i,r_i] [li,ri]相当于把 [ r i + 1 , r i + r i − l i + 1 ] [r_i+1,r_i+r_i-l_i+1] [ri+1,ri+ri−li+1]这个区间和 [ l i , r i ] [l_i,r_i] [li,ri]绑定,我们把这个区间删除,然后用并查集指向 [ l i , r i ] [l_i,r_i] [li,ri]。
然后删除的做法,我们可以标记一下删除,然后查询位置的时候用树状数组查询即可。
#include
#include
#include
#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;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7184
给出 n n n个数字的一个序列,你每次可以选择一个区间将其变为它们所有数的异或和,求将所有数字变成同一个数字,最大化这个数字。
保证有两个相同数字。
考虑一个大于 0 0 0的区间我们都可以将其变为 0 0 0,并且 0 0 0不影响操作,我们可以无视。
而如果有两个数字 x , y x,y x,y之间相隔 1 1 1(中间设为 z z z)但是我们都要保留,那么此时我们可以用那两个相同的数字 a a a,我们将它们变为 x , z , y x o r a , y x o r a x,z,y\ xor\ a,y\ xor\ a x,z,y xor a,y xor a。
然后再异或一次左边两个得到 x , z x o r a x o r y , z x o r a x o r y , y x o r a x,z\ xor\ a\ xor\ y,z\ xor\ a\ xor\ y,y\ xor\ a x,z xor a xor y,z xor a xor y,y xor a。
然后异或出 x , z x o r a x o r y , z , z x,z\ xor\ a\ xor\ y,z,z x,z xor a xor y,z,z,再异或得到 x , a x o r y , z , z x,a\ xor\ y,z,z x,a xor y,z,z,再用另一个 a a a将 x x x异或掉即可。
所以就算求最大异或和,线性基就行了。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7190
一个字符串 s s s,每次可以插入/删除/修改一个字符,求至少操作多少次满足:
我们考虑一下 d p dp dp,看下能不能将复杂度压进 26 n 26n 26n以内。
一个一个字符考虑,然后 d p dp dp。
会发现瓶颈在 f 1 i , j f1_{i,j} f1i,j的转移上,但是我们假设我们目前枚举到 p p p,会发现每次只需要转移 j = a p j=a_p j=ap的方案,所以这部分转移也很好做,我们默认每个字符都会被删除,然后当一个字符不会被删除时会产生 − 1 -1 −1的贡献。
至于插入和修改,因为它们可以是任意字符,我们再开一个状态 26 26 26表示可以是任意字符就好了。
#include
#include
#include
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;
}
题目链接: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 n n且值域为 [ 1 , m ] [1,m] [1,m]的序列满足每一个数都是下一个数的因数。
我们反过来填,每个数字后面只能填它的倍数,那么如果第一个数是
a
1
a_1
a1,
a
2
a_2
a2就只有
⌊
m
a
1
⌋
\lfloor\frac{m}{a_1}\rfloor
⌊a1m⌋个数字可以填。
我们考虑如果每次至少加一个因数,那么这个序列长度不超过
log
m
\log m
logm,然后我们再用组合数把这些加因数的位置分配到
n
n
n里面就行了。
设
f
i
,
j
f_{i,j}
fi,j表示
⌊
m
a
l
⌋
=
i
\lfloor\frac{m}{a_l}\rfloor=i
⌊alm⌋=i,且增加了
j
j
j次因数时的答案,这样
i
i
i可以通过整除分块得到
n
\sqrt n
n级别种取值,转移时也是整除分块去考虑加的因数就好了。
时间复杂度:
O
(
m
3
4
log
m
)
O(m^{\frac{3}{4}}\log m)
O(m43logm)
有不带 log \log log的 min25 \text{min25} min25筛但是我不会。
#pragma GCC optimize(2)
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include
#include
#include
#include
#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;
}
http://acm.hdu.edu.cn/showproblem.php?pid=7221
有一个长度为 n n n的序列,求将它分割成若干连续段的方案满足:
一个朴素的想法是我们去搞两个单调栈去维护最小最大值,然后以这两个单调栈里的数去分割成若干个区间,每个区间产生贡献的方式不同。
但是我们发现一个单调栈改变时可能对应另一个单调栈里的多个区间,所以不能暴力修改,我们用线段树维护每个单调栈里(不考虑令一个单调栈)产生贡献的数字奇数和偶数位置的和,然后修改时查询即可。
#include
#include
#include
#include
#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 n n段的数轴,第 i i i个点上有数字 a i a_i ai,通过 ( i , i + 1 ) (i,i+1) (i,i+1)要求存在之前经过的某个 a x a_x ax满足 b i ∣ a x b_i|a_x bi∣ax。
q q q次询问 x x x能否到达 y y y。
一个点能到达的点肯定是一个区间,暴力的想法是左右两边扩展。实际上暴力加上一个记忆化就能过。
我们假设 x x x能到达 y y y, x + 1 x+1 x+1能到达 y y y但是 x + 1 x+1 x+1不能到达 x x x,那么此时 x + 1 ∼ y x+1\sim y x+1∼y这段路上 b i b_i bi的因数 x + 1 x+1 x+1和 x x x都有,但是 b x b_x bx这个因数 x + 1 x+1 x+1没有 x x x有,也就是 x x x比 x + 1 x+1 x+1多一个因数。
所以这种情况不会往左出现很多,因为 x x x需要不停增大,所以我们从左往右暴力,一个点暴力时如果能到达它左边的点就让它能到达的区间并上左边那个点能到达的区间即可。
时间复杂度: O ( ( q + n ) log n ) O((q+n)\log n) O((q+n)logn)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7226
有一个排列 p p p, x x x和 y y y之间的边权权值为 ∣ x − y ∣ × ∣ p x − p y ∣ |x-y|\times |p_x-p_y| ∣x−y∣×∣px−py∣,求最小生成树。
因为 p p p是排列,会发现合并两个连通块的代价不超过 n − 1 n-1 n−1(连接最近的两个)。
所以我们直接删除代价大于 n − 1 n-1 n−1的边即可,那么剩下的对数满足 min { ∣ i − j ∣ , ∣ p i − p j ∣ } ≤ n \min\{|i-j|,|p_i-p_j|\}\leq \sqrt n min{∣i−j∣,∣pi−pj∣}≤n,所以暴力枚举 n \sqrt n n以内的对然后记下每一条合法的边跑最小生成树。
因为边权不超过 n n n所以可以直接桶排。
时间复杂度: O ( n n α ( n ) ) O(n\sqrt n\alpha(n)) O(nnα(n))
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=7232
求有多少个长度为 m m m,值域为 n n n的序列满足每一个长度为 n n n的连续段都不是一个 1 ∼ n 1\sim n 1∼n的排列。
考虑容斥,钦定一些区间是 1 ∼ n 1\sim n 1∼n的排列,那么如果相邻位置 x , y x,y x,y两个不超过 n n n贡献是 ( y − x ) ! (y-x)! (y−x)!,否则是 n ! × n y − x n!\times n^{y-x} n!×ny−x。
分治 N T T NTT NTT或者多项式求逆计算即可。