2022 HDU多校8
Theramore(思维)
Problem
给定一个01
串,可以进行无限次操作,每次操作可以把一个长度为奇数的区间翻转,问可以得到的字典序最小的01
串是多少
Solve
hit1
:反转后奇数位置还是在奇数位置,偶数位置还是在偶数位置
因此上面操作其实相当于我们可以把一个奇数位置移动到任意奇数位置,偶数位置移动到任意偶数位置,所以对偶数位置和奇数位置分开讨论,贪心,每次都先选0
来填即可。
Code
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
char s[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
cin>>(s+1);
int n=strlen(s+1);
vector<int>one(2),zero(2);
for(int i=1;s[i];i++)
if(s[i]=='1'){
one[i&1]++;
}else zero[i&1]++;
for(int i=1;s[i];i++){
if(zero[i&1]){
cout<<0;
zero[i&1]--;
}else{
cout<<1;
one[i&1]--;
}
}
cout<<'\n';
}
}
Darkmoon Faire(数据结构维护DP、单调栈、线段树)
Problem
一个序列\(\{a\}\)是好的序列是它的最大数在奇数位置,最小数在偶数位置,保证\(\{a\}\)中没有两个相同的数。现在给定一个序列\(A\),问有多少种区间划分方式,使得每个区间都是好的序列,保证\(A\)中不存在两个相同的数
\(1\le n\le 3\times 10^5\)
Solve
暴力做法用\(dp_{i}\)表示前\(i\)个数划分的方案数,容易想到\(O(n^2)\)的做法,考虑优化
首先假设当前要转移的位置来到\(i\),那么\(dp_{i}\)可以从\(dp_j\)转移的前提是\([j+1,i]\)这段区间满足上述条件。假设这段区间最大值的位置在原序列中是\(x\),那么\(j\)的位置要和\(x\)同奇偶才可以转移,最小值的位置是\(y\),那么\(j\)的位置要和\(y\)的奇偶性不同才可以转移。
如果我们开\(2\)个数组$$odd,even\(来分别表示奇数和偶数,那么假如位置\)j\(是奇数,那么如果\)[j+1,i]\(中的最大值在奇数位置,我们在\)odd[j]\(打上一个标记,如果\)[j+1,i]\(中的最小值在偶数位置,我们还是在\)odd[i]\(打上一个标记,因为这个位置的奇偶性要和最小值的奇偶性相反,而\)j\(是偶数也是同样的道理。也就是说最后一个位置\)j\(是奇数,那么\)odd[j]=2\(它就是可以被转移的,如果是偶数,则\)even[j]=2$才可以被转移,也就是题目要满足的两个条件。
那么我们是不是可以考虑每个数作为最大值或最小值的时候可以标记的区间范围。我们创建两棵线段树\(T[0/1]\)分别表示偶数位置和奇数位置,\(T[0]\)维护所有偶数位置的\(dp\)和以及每个偶数位置的标记,\(T[1]\)同理维护。考虑当前的位置\(i\),利用单调栈可以很快求出左边第一个大于\(a_i\)的数的位置\(l\),那么\([l+1,i]\)这段区间都是以\(a_i\)作为最大值,那么我们在\(T[i\&1]\)中把这段区间的标记都加一,表示前面与最大值出现位置同奇偶的位置的标记(满足条件)\(+1\),我们是直接区间加,不考虑奇偶性不同的位置,因为我们最后求的是\(dp\)和,而我们奇数位置的\(dp\)和只会被单点修改在\(T[1]\)中,偶数位置也同理,所以就算统计奇偶性不同的位置,其贡献也不会被统计进来。
另外,在单调栈求\(l\)的过程中,我们要把弹出栈的位置作为极值的时候所覆盖的区间标记修改,假设\(stk\)是求左边最大值的栈,那么\([stk[top-1]+1,stk[top]]\)所在的区间时以\(a_{stk[top]}\)作为最值的,但加入\(a_i\)之后,最值改变了,所以我们需要撤销掉\(a_{stk[top]}\)对区间\([stk[top-1]+1,stk[top]]\)的标记。
最后转移这里要错移一位,因为我们是从\(dp_j\)转移过来的
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int N=3e5+10;
struct Segment{
struct node{
ll sum,addtag;
int tag;
}tr[N*4];
#define ls rt<<1
#define rs rt<<1|1
void build(int rt,int l,int r){
tr[rt].sum=tr[rt].addtag=tr[rt].tag=0;
if(l==r) return;
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void pushup(int rt){
tr[rt].tag=max(tr[ls].tag,tr[rs].tag);
tr[rt].sum=0;
if(tr[rt].tag==tr[ls].tag) tr[rt].sum=(tr[rt].sum+tr[ls].sum)%mod;
if(tr[rt].tag==tr[rs].tag) tr[rt].sum=(tr[rt].sum+tr[rs].sum)%mod;
}
void pushdown(int rt){
tr[ls].tag+=tr[rt].addtag;
tr[rs].tag+=tr[rt].addtag;
tr[ls].addtag+=tr[rt].addtag;
tr[rs].addtag+=tr[rt].addtag;
tr[rt].addtag=0;
}
void update1(int rt,int l,int r,int pos,ll x){
if(l==pos && r==pos){
tr[rt].sum=x;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) update1(ls,l,mid,pos,x);
else update1(rs,mid+1,r,pos,x);
pushup(rt);
}
void update2(int rt,int L,int R,int l,int r,int x){
if(l<=L&&R<=r){
tr[rt].tag+=x;
tr[rt].addtag+=x;
return;
}
pushdown(rt);
int mid=(L+R)>>1;
if(l<=mid) update2(ls,L,mid,l,r,x);
if(r>mid) update2(rs,mid+1,R,l,r,x);
pushup(rt);
}
ll query(){
if(tr[1].tag==2) return tr[1].sum;
else return 0;
}
}T[2];
int stk1[N],top1; //栈里面单调递增(从栈底到栈顶)
int stk2[N],top2; //栈里面单调递减(从栈底到栈顶)
ll dp[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
T[0].build(1,1,n);
T[1].build(1,1,n);
top1=top2=0;
vector<int>a(n+1);
for(int i=1;i<=n;i++) cin>>a[i],dp[i]=0;
dp[0]=1;
for(int i=1;i<=n;i++){
//先维护每个当前点i作为最大和最小可以影响的区间
T[i&1].update1(1,1,n,i,dp[i-1]);
//a[i]作为最大值
while( top1 && a[i]>a[stk1[top1]]){
T[stk1[top1]&1].update2(1,1,n,stk1[top1-1]+1,stk1[top1],-1);
top1--;
}
T[i&1].update2(1,1,n,stk1[top1]+1,i,1);
stk1[++top1]=i;
while(top2 && a[i]<a[stk2[top2]]){
T[(stk2[top2]&1)^1].update2(1,1,n,stk2[top2-1]+1,stk2[top2],-1);
top2--;
}
T[(i&1)^1].update2(1,1,n,stk2[top2]+1,i,1);
stk2[++top2]=i;
dp[i]=(T[0].query()+T[1].query())%mod;
}
cout<<dp[n]<<'\n';
}
}
Quel'Thalas
Problem
问最少需要画几条不过原点的直线,使得可以经过所有\(\{(i,j)|i,j\in [0,n]\}/(0,0)\)。
Solve
\(i+j \in [1,2n]\),所以作直线\(x+y=b\),其中\(b\in [1,2n]\)即可,所以答案是\(2n\)
Orgrimmar(克鲁斯卡尔、根号复杂度)
Problem
给定一个长度为\(n\)的排列\(p\),任意两个点\((i,j)\)之间连边的代价为\(|i-j||p_i-p_j|\),问这\(n\)个点连通需要的最小代价
\(1\le n\le 5\times 10^5\)
Solve
如果按照相邻下标相互连边,发现边权最大只能是\(n-1\)。所以我们不考虑边权大于\(n-1\)的情况,因为显然不会更优。只考虑边权在\(1\)到\(n-1\)之间,那么发现\(|i-j|\)和\(|p_i-p_j|\)之间必定有一个小于\(\sqrt{n}\),所以我们枚举他们的取值。
- 枚举\(|i-j|\)的取值,在\(1\)到\(\sqrt{n}\)的范围内,然后枚举\(i\)是多少,根据\(|i-j|\)和\(i\)得到\(j\),然后连边
- 枚举\(|p_i-p_j|\)的取值,在\(1\)到\(\sqrt{n}\)的范围内,然后枚举\(p_i\)是多少,根据\(|p_i-p_j|\)和\(p_i\)得到\(p_j\),然后连边
最后按照跑一边最小生成树即可,时间复杂度是\(O(n\sqrt{n})\)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=500005;
int f[N],p[N],pos[N];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n;
cin>>n;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=n;i++){
cin>>p[i];
pos[p[i]]=i;
}
ll ans=0;
int m=sqrt(n)+1;
vector<vector<pair<int,int>>>e(n+1);
//枚举|i-j|的值
for(int i=1;i<=m;i++){
for(int j=1;j+i<=n;j++){
int w=i*abs(p[i+j]-p[j]);
if(w<n) e[w].emplace_back(i+j,j);
}
}
//枚举|pi-pj|的值
for(int i=1;i<=m;i++){
for(int j=1;j+i<=n;j++){
int w=i*abs(pos[i+j]-pos[j]);
if(w<n) e[w].emplace_back(pos[i+j],pos[j]);
}
}
int cnt=0;
//从小到大枚举边权
for(int i=1;i<=n&&cnt<n-1;i++){
for(auto t:e[i]){
if(find(t.first)!=find(t.second)){
f[find(t.first)]=find(t.second);
ans+=i;
cnt++;
if(cnt==n-1) break;
}
}
}
cout<<ans<<'\n';
}
}
Orgrimmar(树形DP)
Problem
给定一个\(n\)个点的树,从中选出一些点,使得其导出子图中每个点的度数不超多\(1\),问最多可以选择几个点。
\(1\le n\le 5\times 10^5\)
Solve
- \(dp_{i,0}\):不选\(i\)这个点
- \(dp_{i,1}\):选\(i\)这个点,并且最后它的度数是\(0\),即与它直接相连的点都不选
- \(dp_{i,2}\):选\(i\)这个点,并且最后它的度数是\(1\),即与它直接相连的点恰好选一个
转移:
- \(dp_{i,0}=\sum_{j} \max(dp_{j,0},dp_{j,1},dp_{j,1})\)
- \(dp_{i,1}=1+\sum_{j}dp_{j,0}\)
- \(dp_{i,2}=1+\max(dp_{j^{'},1}+\sum_{j\ne j^{'}}dp_{j,0})\)
Code
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int dp[N][3];
vector<int>E[N];
void dfs(int u,int fa){
dp[u][1]=dp[u][2]=1;
for(auto v:E[u]){
if(v==fa) continue;
dfs(v,u);
dp[u][0]+=max({dp[v][0],dp[v][1],dp[v][2]});
dp[u][1]+=dp[v][0];
}
for(auto v:E[u]){
if(v==fa) continue;
dp[u][2]=max(dp[u][2],dp[v][1]+dp[u][1]-dp[v][0]);
}
};
int main(){
int size(512<<20); // 512M
__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) E[i].clear(),dp[i][0]=dp[i][1]=dp[i][2]=0;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,1);
printf("%d\n",max({dp[1][0],dp[1][1],dp[1][2]}));
}
exit(0);
}
Vale of Eternal(计算几何)
Problem
二维平面上一开始有\(n\)个点。\(t\)时刻时这\(n\)个点会变成\(4n\)个点,每个点会变成\((x_i+t,y_i),(x_i-t,y_i),(x_i,y_i-t),(x_i,y_i+t)\)。然后有\(q\)次询问,给定一个时刻\(t\),问在时刻\(t\)的手这\(4n\)个点围成的凸包的面积。\(1\le n,q\le 10^5\)
Solve
Code
Stormwind
Problem
给定 \(n \times m\) 的矩形,横平竖直的切分尽可能多刀使得每块子矩形的面积均大于等于\(k\),并且剩下矩阵的长宽都是整数,问最多切多少刀
\(1\le n,m,k\le 10^5\)
Solve
枚举最后每块子矩形的长是多少,假设是\(x\),那么宽\(y\)至少要\(\lceil \frac{k}{x} \rceil\),那么答案就是\(\max(\lfloor \frac{n}{x} \rfloor + \lfloor \frac{m}{y} \rfloor-2)\)
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,m,k;
cin>>n>>m>>k;
int ans=0;
for(int x=1;x<=k && x<=n;x++){
int y=(k+x-1)/x;
if(y>m) continue;
ans=max(ans,n/x+m/y-2);
}
cout<<ans<<'\n';
}
}
Shattrath City(多项式、DP)
Problem
求长度为\(m\),并且每个元素的取值范围是\([1,n]\),且任意连续\(n\)个数字不能是长度为\(n\)的排列的序列的个数
\(1\le n,m\le 2\times 10^5\)