Hint:可以点击右下角的目录符号快速跳转到指定位置
上接:SSF信息社团寒假训练题目整理(三)
下接:SSF信息社团4月训练题目整理
3.5
995A
先将能归位的车都归位,然后让所有车按照图中顺序不断地转:
每次转完后都检查每辆车是否能归位即可。最坏情况是每辆车都转了一圈,即 (2n imes 2n=10000) 次操作,再加上进入车位的操作数,共 (10000+100<20000) 次,可以通过。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=50;
int a[5][N+10];
int n,k;
vector<pair<int,pair<int,int> > > ans;
void print(pair<int,pair<int,int> > x)
{printf("%d %d %d
",x.first,x.second.first,x.second.second);}
pair<int,pair<int,int> > Make(int x,int y,int z)
{return make_pair(x,make_pair(y,z));}
void see()
{
puts("--------");
for(int i=2;i<=3;i++)
{
for(int j=1;j<=n;j++) printf("%d ",a[i][j]);
puts("");
}
puts("========");
}
void move()
{
for(int i=2;i<=3;i++)
{
for(int j=1;j<=n;j++)
{
if(!a[i][j]) continue;
if(i==2&&a[1][j]==a[2][j])
{
ans.push_back(Make(a[2][j],1,j));
a[2][j]=0;
}
if(i==3&&a[4][j]==a[3][j])
{
ans.push_back(Make(a[3][j],4,j));
a[3][j]=0;
}
}
}
}
pair<int,int> nxt(int i,int j)
{
if(i==2)
{
if(j==n) return make_pair(3,n);
else return make_pair(2,j+1);
}
else
{
if(j==1) return make_pair(2,1);
else return make_pair(3,j-1);
}
}
int &nxta(int i,int j){return a[nxt(i,j).first][nxt(i,j).second];}
pair<int,int> pre(int i,int j)
{
if(i==2)
{
if(j==1) return make_pair(3,1);
else return make_pair(2,j-1);
}
else
{
if(j==n) return make_pair(2,n);
else return make_pair(3,j+1);
}
}
int &prea(int i,int j){return a[pre(i,j).first][pre(i,j).second];}
void rotate()
{
int fi=0,fj=0;
for(int i=2;i<=3;i++)
{
for(int j=1;j<=n;j++)
{
if(!a[i][j]&&nxta(i,j))
{
fi=i;
fj=j;
break;
}
}
if(fi||fj) break;
}
for(int ii=nxt(fi,fj).first,jj=nxt(fi,fj).second;ii!=fi||jj!=fj;)
{
prea(ii,jj)=a[ii][jj];
if(a[ii][jj])
ans.push_back(Make(a[ii][jj],pre(ii,jj).first,pre(ii,jj).second));
a[ii][jj]=0;
pair<int,int> tmp=nxt(ii,jj);
ii=tmp.first;
jj=tmp.second;
}
}
bool check()
{
for(int i=2;i<=3;i++)
{
for(int j=1;j<=n;j++)
if(a[i][j])
return false;
}
return true;
}
bool check1()
{
for(int i=2;i<=3;i++)
{
for(int j=1;j<=n;j++)
if(!a[i][j])
return true;
}
return false;
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=4;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
move();
if(!check1()) return printf("-1"),0;
while(!check())
{
rotate();
move();
}
printf("%d
",(int)ans.size());
for(int i=0;i<ans.size();i++) print(ans[i]);
return 0;
}
993C
战机有可能在的位置只能在左边某个点和右边某个点的中点上,最多 (n imes m=360) 个。对每一个可能的点用 bitset/long long 存一下两边哪些点的中点在这里,然后 (n^2m^2) 暴力枚举战机在哪两个点并计算覆盖到了两边哪些点即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
int cal(ll n)
{
int ans=0;
while(n)
{
ans++;
n-=(n&-n);
}
return ans;
}
const int N=60;
int a[N+10],b[N+10];
ll sx[40000+10],sy[40000+10];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
set<int> s;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
sx[a[i]+b[j]+20000]|=(1ll<<i-1);
sy[a[i]+b[j]+20000]|=(1ll<<j-1);
s.insert(a[i]+b[j]);
}
}
int ans=0;
for(set<int>::iterator it1=s.begin();it1!=s.end();it1++)
{
for(set<int>::iterator it2=s.begin();it2!=s.end();it2++)
{
int tx=*it1,ty=*it2;
ans=max(ans,cal(sx[tx+20000]|sx[ty+20000])+cal(sy[tx+20000]|sy[ty+20000]));
}
}
printf("%d",ans);
return 0;
}
1000E
容易观察出必须经过的点一定是桥(割边)。把边双缩一下点,得到一棵树,求树的直径即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=6e5,M=6e5;
int head[N+10],ver[M+10],nxt[M+10],tot=1;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
bool f[N+10];
int low[N+10],dfn[N+10],num;
void tarjan(int x,int in)
{
dfn[x]=low[x]=++num;
// printf("x:%d
",x);
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y,i);
low[x]=min(low[x],low[y]);
if(low[y]>low[x]) f[i]=f[i^1]=1;
}
else if(i!=(in^1))
low[x]=min(low[x],dfn[y]);
}
}
int col[N+10];
void dfs(int x,int co)
{
col[x]=co;
for(int i=head[x];i;i=nxt[i])
{
if(f[i]) continue;
int y=ver[i];
if(!col[y])
dfs(y,co);
}
}
int Head[N+10],Ver[M+10],Nxt[M+10],Tot=0;
void Add(int x,int y)
{
Ver[++Tot]=y;
Nxt[Tot]=Head[x];
Head[x]=Tot;
}
bool vis[N+10];
int dis[N+10];
pair<int,int> bfs(int x)
{//first:distance, second:vertex
memset(dis,0,sizeof(dis));
memset(vis,0,sizeof(vis));
pair<int,int> ans=make_pair(0,0);
queue<pair<int,int> > que;
que.push(make_pair(0,x));
vis[x]=1;
while(!que.empty())
{
pair<int,int> x=que.front(); que.pop();
ans=max(ans,x);
for(int i=Head[x.second];i;i=Nxt[i])
{
int y=Ver[i];
if(vis[y]) continue;
vis[y]=1;
dis[y]=dis[x.second]+1;
que.push(make_pair(dis[x.second]+1,y));
}
}
return ans;
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i,0);
// for(int i=2;i<=tot;i+=2)
// if(f[i])
// printf("%d->%d
",ver[i^1],ver[i]);
int cnt=0;
for(int i=1;i<=n;i++)
if(!col[i])
dfs(i,++cnt);
// for(int i=1;i<=n;i++) printf("col[%d]:%d
",i,col[i]);
// puts("");
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=nxt[j])
if(col[i]!=col[ver[j]])
Add(col[i],col[ver[j]]);
}
// for(int i=1;i<=cnt;i++)
// {
// printf("i=%d:",i);
// for(int j=Head[i];j;j=Nxt[j]) printf("%d ",Ver[j]);
// puts("");
// }
printf("%d",bfs(bfs(1).second).first);
return 0;
}
3.6
1433F
对每一行进行 dp,令 (f(i,j,k)) 表示当前一行考虑前 (i) 列,共选了 (j) 个数,和余数为 (k) 的和的最大值。随便转移一下求出所有 (lin[0,k-1]) 的 (f(m,j,l)) 的最大值,把这 (k) 个最大值就相当于第 (i) 行的物品,那么题意变成了:已知有 (n) 组带权值物品,每组物品都有 (k) 个,每个组只能选一个物品,求在所有物品权值的和是 (k) 的倍数的情况下和的最大值。令 (dp(i,j)) 表示前 (i) 组,当前和的余数为 (j) 的答案,转移一下,(dp(n,0)) 就是答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
inline void read(int &x)
{
x=0; int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
const int N=80;
int f[N][N][N],tmp[N];
int a[N][N],n,m,k;
vector<int> v[N];
void Dp(int r)//row
{
memset(f,-0x3f,sizeof(f));
// f[0][0][0]=0;
for(int i=0;i<=m;i++) f[i][0][0]=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=m/2;j++)
{
for(int l=0;l<k;l++)
{
f[i][j][l]=f[i-1][j][l];
if(j) f[i][j][l]=max(f[i][j][l],f[i-1][j-1][((l-a[r][i])%k+k)%k]+a[r][i]);
// printf("((%d-%d)mod%d+%d)mod%d:%d
",l,a[r][i],k,k,k,((l-a[r][i])%k+k)%k);
}
}
}
memset(tmp,-0x3f,sizeof(tmp));
for(int j=0;j<=m/2;j++)
for(int l=0;l<k;l++)
tmp[l]=max(tmp[l],f[m][j][l]);
for(int i=0;i<k;i++) v[r].push_back(tmp[i]);
}
int dp[N][N];
int main()
{
read(n);read(m);read(k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
read(a[i][j]);
for(int i=1;i<=n;i++) Dp(i);
// for(int i=1;i<=n;i++)
// {
// printf("i:%d
",i);
// for(int j=0;j<v[i].size();j++) printf("%d ",v[i][j]);
// puts("");
// }
memset(dp,-0x3f,sizeof(dp));
for(int i=0;i<=n;i++)
dp[i][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<k;j++)
{
dp[i][j]=dp[i-1][j];
for(int l=0;l<v[i].size();l++)
dp[i][j]=max(dp[i][j],dp[i-1][((j-v[i][l])%k+k)%k]+v[i][l]);
}
}
printf("%d",dp[n][0]);
return 0;
}
1433G
做过,丢链接跑路。
1446C
题目等价于问最大保留数量使得有且仅有一对数的边重复。把所有 (a_i) 插到 Trie 树里,则与 (a_i) 异或值最小的点 (a_j) 一定在 (operatorname{LCA}(a_i,a_j)) 里,且 (operatorname{LCA}(a_i,a_j)) 是所有 (operatorname{LCA}(a_i,a_k);(k ot=i)) 中最小的。为了有且仅有一对数的边重复,需要把 Trie 树变成这样:
考虑 dp。令 (f(i)) 表示以 (i) 为根的子树的最大保留数量,那么有转移:
答案就是 (n-f(0))((0) 为 Trie 的根)。
#include<bits/stdc++.h>
using namespace std;
inline void read(int &x)
{
x=0; int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
const int N=2e5+10;
int tr[N*30][2],cnt=0;
void ins(int x)
{
int pos=0;
for(int i=30;i>=0;i--)
{
bool z=x&(1<<i);
if(!tr[pos][z])
tr[pos][z]=++cnt;
pos=tr[pos][z];
// sz[pos]++;
}
}
int f[N*30];
void Dp(int pos)
{
if(!tr[pos][0]&&!tr[pos][1])
{
f[pos]=1;
return;
}
if(tr[pos][0]&&tr[pos][1])
{
Dp(tr[pos][0]);
Dp(tr[pos][1]);
f[pos]=max(f[tr[pos][0]],f[tr[pos][1]])+1;
return;
}
if(tr[pos][0])
{
Dp(tr[pos][0]);
f[pos]=max(f[pos],f[tr[pos][0]]);
}
if(tr[pos][1])
{
Dp(tr[pos][1]);
f[pos]=max(f[pos],f[tr[pos][1]]);
}
}
int a[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ins(a[i]);
}
Dp(0);
printf("%d",n-f[0]);
return 0;
}
3.7
319B
维护一个栈底到栈顶递增的单调栈,令 (f(i)) 表示神经 病 (i) 需要几轮才能被杀死(若不死则 (f(i)=0)),则:
- 若
while(top&&a[st[top]]<a[i]) top--
操作执行完后栈为空,(f(i)=0),这个人死不了。 - 若一个元素都没弹出,(f(i)=1),因为会被他下面的神经病直接杀死。
- 若弹出了 (k) 个人((k>0))(j_1,j_2,cdots,j_k),(f(i)=maxlimits_{l=1}^k{f(j_l)}),因为栈底需要杀死这 (k) 个神经病 (i) 才能死。
答案为 (maxlimits_{i=1}^n{f(i)})。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int st[N],top=0;
int main()
{
int n;
scanf("%d",&n);
vector<int> f(n+1);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int ans=0;
for(int i=1;i<=n;i++)
{
int mx=0;
while(top&&a[st[top]]<a[i])
{
mx=max(mx,f[st[top]]);
top--;
}
if(!top)
f[i]=0;
else
f[i]=mx+1;
st[++top]=i;
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}
321B
考虑两种贪心策略:
- 打完手牌,也就是打消耗战,先把对方的
DEF
消耗完,然后把ATK
消耗完,最后打真伤害。消耗过程中要选择与对方手牌点数尽量近的,因为要使真伤害尽量大; - 不打完手牌,只打对方的
ATK
,每次尽量用大的打小的。
可以发现两种贪心策略如果单独使用都会挂,所以要同时使用然后取 (max)。
#include<bits/stdc++.h>
using namespace std;
const int N=100+10;
#define int long long
int atk[N],def[N],a[N];
int ca,cd,n,m;
bool vis[N];
int sol1()
{
if(m<n) return -1;
int ans=0,cnt=0;
for(int i=1,j=1;i<=m&&j<=cd;j++,i++)
{
while(a[i]<=def[j])
i++;
vis[i]=1;
cnt++;
}
// puts("VIS:");
// for(int i=1;i<=m;i++) putchar('0'^vis[i]);
// puts("");
for(int i=1,j=1;i<=m&&j<=ca;j++,i++)
{
while(a[i]<atk[j]||vis[i])
{
i++;
if(i>m) break;
}
if(i>m) break;
vis[i]=1;
ans+=a[i]-atk[j];
cnt++;
}
// printf("cnt:%lld
",cnt);
if(cnt!=n) return -1;
for(int i=1;i<=m;i++)
if(!vis[i])
ans+=a[i];
return ans;
}
int sol2()
{
int ans=0;
// memset(vis,0,sizeof(vis));
for(int i=m,j=1;i>=1&&j<=ca;j++,i--)
{
// printf("i=%lld, j=%lld
",i,j);
// printf("a[i]:%lld, atk[j]:%lld
",a[i],atk[j]);
while(a[i]<atk[j]&&i>0)
i--;
if(i<=0) continue;
ans+=a[i]-atk[j];
}
return ans;
}
signed main()
{
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;i++)
{
char s[5];
int x;
scanf("%s%lld",s,&x);
if(!strcmp(s,"ATK"))
atk[++ca]=x;
else
def[++cd]=x;
}
for(int i=1;i<=m;i++)
scanf("%lld",&a[i]);
sort(a+1,a+m+1);
sort(atk+1,atk+ca+1);
sort(def+1,def+cd+1);
// printf("sol1:%lld, sol2:%lld
",sol1(),sol2());
printf("%lld",max(sol1(),sol2()));
return 0;
}
327D
要尽量放 red towers。考虑一种贪心,对图进行 DFS,如果当前到达的点 ((i,j)) 不是 big hole,就先放一个 blue tower,然后往后面递归,递归回来之后递归树内的儿子节点都变成了红的,此时把 ((i,j)) 的 blue tower 搞掉,放成红的。至于儿子怎么处理,请再读一遍刚才那句话。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=500;
char s[N+10][N+10];
bool vis[N+10][N+10];
int n,m;
vector<pair<char,pair<int,int> > > ans;
pair<char,pair<int,int> > make(char s,int a,int b) {return make_pair(s,make_pair(a,b));}
void print(pair<char,pair<int,int> > x) {printf("%c %d %d
",x.first,x.second.first,x.second.second);}
const int nxt[4][2]={0,1,1,0,0,-1,-1,0};
void dfs(int x,int y,bool flag)
{
vis[x][y]=1;
ans.push_back(make('B',x,y));
for(int i=0;i<4;i++)
{
int tx=nxt[i][0]+x,ty=nxt[i][1]+y;
if(tx<1 || tx>n || ty<1 || ty>m || vis[tx][ty] || s[tx][ty]=='#') continue;
// printf("tx:%d, ty:%d
",tx,ty);
// vis[tx][ty]=1;
dfs(tx,ty,false);
}
if(!flag)
{
ans.push_back(make('D',x,y));
ans.push_back(make('R',x,y));
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]=='.'&&!vis[i][j])
dfs(i,j,1);
printf("%d
",(int)ans.size());
for(int i=0;(unsigned)i<ans.size();i++) print(ans[i]);
return 0;
}
3.8
724D
注意到一个答案一定是形如下面的形式:(mathtt{aadots aabbdots bbdots zzz}),也就是除了最后一个字符,其他字典序小于它的字符都放进答案里。可以通过 (Theta(26n)) 的时间枚举出最后一个字符是谁,不妨设为 (x),然后在字符串 (s) 中把所有字典序 (<x) 的字符都标记出来,这些都是必定在答案中的,然后通过贪心来枚举最少放多少个 (x) 能使整个字符串满足条件。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5;
char s[N+10];bool vis[N+10];
int n,m;
int get()
{
for(int i=0;i<26;i++)
{
for(int j=1;j<=n;j++)
if(s[j]-'a'<=i)
vis[j]=1;
int lst=0,mx=0;
for(int j=1;j<=n;j++)
{
if(vis[j])
{
mx=max(mx,j-lst);
lst=j;
}
}
mx=max(mx,n-lst);
// printf("i:%d, mx:%d
",i,mx);
if(mx<=m) return i;
}
}
int c[26];
// bool vis[N+10];
int dis[N+10];
int main()
{
scanf("%d%s",&m,s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
c[s[i]-'a']++;
int k=get();
int lst=0;
int cnt=0,lstvis=0;
memset(vis,0,sizeof(vis)); // for(int i=1;i<=n;i++)
// printf("%d",vis[i]);
for(int i=1;i<=n;i++)
if(s[i]-'a'<k)
vis[i]=1;
vector<int> v;
for(int i=1;i<=n;i++)
if(s[i]-'a'==k)
v.push_back(i);
// for(int i=0;i<v.size();i++) printf("%d ",v[i]);
// puts("");
for(int i=1,j=0;i<=n;i++)
{
if(vis[i]) lst=i;
if(i-lst>=m)
{
while(j<v.size()&&v[j]<=i)
j++;
j--;
lst=v[j];
cnt++;
}
}
for(int i=0;i<k;i++)
for(int j=1;j<=c[i];j++)
putchar('a'+i);
for(int i=1;i<=cnt;i++)
putchar('a'+k);
return 0;
}
729E
合法的序列排序后一定是这样的:(0,1,1,2,2,3,3,cdots,x),(forall iin[1,x],exists a_{j}=x-1)。于是可以把这些数都放到一个桶里,贪心地把最大的、多余的数补给到较小的数中。要注意特判 (a_s ot=0) 或者 (exists i ot=s,a_i=0) 的情况。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],cnt[N];
int n,k;
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int ans=0;
int c=0;
for(int i=1;i<=n;i++)
{
if(!a[i] && i!=k)
c++;
else if(i!=k) cnt[a[i]]++;
}
ans+=c;
if(a[k]) ans++;
int j = n-1;
while(!cnt[j]) j--;
for(int i=1;i<=n-1&&j>=1&&i<j;i++)
{
if(cnt[i]) continue;
if(c)
{
c--;
continue;
}
while(!cnt[j]) j--;
if(i>=j) break;
cnt[i]++;
cnt[j]--;
ans++;
// out();
}
printf("%d",ans);
return 0;
}
811C
使用 (mathcal O(n^2)) 的时间复杂度预处理每个区间 ([l,r]) 是否合法,如果合法就顺便处理出 (a_loperatorname{xor}a_{l+1}operatorname{xor}cdotsoperatorname{xor}a_r)。令 (f(i)) 表示从 (1) 到 (i) 分割出若干个区间能得到的最优答案,令 (f(0)=0),则有:
由于 (nle 5000),(mathcal O(n^2)) 暴力转移即可,(f(n)) 即为答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline void read(int &x)
{
x=0; int f=1;
char c=getchar();
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0' && c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
const int N=5010;
int sum[N][N];
bool f[N][N];
int a[N],dp[N];
int cnt[N],cnt1[N];
int main()
{
int n;
read(n);
for(int i=1;i<=n;i++)
{
read(a[i]);
cnt[a[i]]++;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=5000;j++) cnt1[j]=0;
int zzt=0;
for(int j=i;j<=n;j++)
{
sum[i][j]=sum[i][j-1];
if(!cnt1[a[j]]) sum[i][j]^=a[j];
if(!cnt1[a[j]]) zzt++;
cnt1[a[j]]++;
if(cnt1[a[j]]==cnt[a[j]]) zzt--;
if(zzt==0) f[i][j]=1;
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++)
{
dp[i]=max(dp[i],dp[j]);
if(f[j+1][i]) dp[i]=max(dp[i],dp[j]+sum[j+1][i]);
}
}
printf("%d",dp[n]);
return 0;
}
3.9
835D
(f(i,j)) 表示 ([i,j]) 的回文阶数。若 (s_{i}s_{i+1}cdots s_j) 不是回文串(用哈希判),则 (f(i,j)) 设为 (0);否则,先把 (f(i,j)) 设成 (1),如果左半部分和右半部分相等,就把左半部分的 (f) 值加上 (1) 再和 (f(i,j)) 取个 (max)。最后遍历一遍所有 (f(i,j)),每个 (f(i,j)) 相当于给 (1sim f(i,j)) 的答案加了 (1),随便统计一下即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
const ull base=20061269ull;
const int N=5010;
ull p[N],h1[N],h2[N];
char s[N];
int n;
void init()
{
p[0]=1ull;
for(int i=1;i<=n;i++)
{
p[i]=p[i-1]*base;
h1[i]=h1[i-1]*base+s[i];
}
for(int i=n;i;i--) h2[i]=h2[i+1]*base+s[i];
}
ull hs1(int l,int r) {return h1[r]-h1[l-1]*p[r-l+1];}
ull hs2(int l,int r) {return h2[l]-h2[r+1]*p[r-l+1];}
int dp[N][N];
int c[N];
void modify(int x,int d) {for(;x<=n;x+=x&-x) c[x]+=d;}
int query(int x) {int ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
init();
for(int i=1;i<=n;i++) dp[i][i]=1;
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
if(hs1(i,j)==hs2(i,j))
{
dp[i][j]=1;
if(hs1(i,i+len/2-1)==hs2(j-len/2+1,j))
dp[i][j]=max(dp[i][j],dp[i][i+len/2-1]+1);
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
modify(dp[i][j]+1,-1);
modify(1,1);
}
// puts
}
for(int i=1;i<=n;i++) printf("%d ",query(i));
return 0;
}
832D
找一下 (a,b,c) 三个点的分叉点,观察图可知((1) 为根,(a=1,b=3,c=4)),实际上就是找 (operatorname{lca}(a,b),operatorname{lca}(b,c),operatorname{lca}(a,c)) 取深度最大的。设这个点为 (d),那么答案就是 (max{operatorname{dis}(d,a),operatorname{dis}(d,b),operatorname{dis}(d,c)}+1)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10,M=2e5+10;
int head[N],ver[M],nxt[M],tot=0;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int t,n,dep[N],fa[N][30];
bool vis[N];
void init()
{
queue<int> que;
que.push(1);
dep[1]=0;
vis[1]=1;
while(!que.empty())
{
int x=que.front();que.pop();
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(vis[y]) continue;
vis[y]=1;
dep[y]=dep[x]+1;
fa[y][0]=x;
for(int i=1;i<=t;i++)
fa[y][i]=fa[fa[y][i-1]][i-1];
que.push(y);
}
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=t;~i;i--)
if(dep[fa[x][i]]>=dep[y])
x=fa[x][i];
if(x==y) return x;
for(int i=t;~i;i--)
{
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int dis(int x,int y) {return dep[x]+dep[y]-2*dep[lca(x,y)];}
void ckmx(int &x,int y) {x=max(x,y);}
int main()
{
int n,q;
scanf("%d %d",&n,&q);
t=(int)(log(n)/log(2))+1;
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
add(x,i);
add(i,x);
}
init();
for(int i=1;i<=q;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
int l1=lca(x,y),l2=lca(x,z),l3=lca(y,z),tmp=0;
if(dep[l1]>=dep[l2]&&dep[l1]>=dep[l3])
tmp=l1;
else if(dep[l2]>=dep[l1]&&dep[l2]>=dep[l3])
tmp=l2;
else
tmp=l3;
// printf("tmp:%d
",tmp);
printf("%d
",max(max(dis(tmp,x),dis(tmp,y)),dis(tmp,z))+1);
// int ans=0;
}
return 0;
}
839B
假设四个坐的数量为 (four),两个的为 (two),一个的为 (one)。将所有数都尽量放到 (four) 里,其次是 (two),如果过程中 (four,two) 都不过就输出 ( exttt{NO}),然后将剩下的从大到小排个序;遍历剩下的人,如果剩下了 (3),就优先放到一个 (four) 里,其次是两个 (two),都不行就把它拆成 (1) 和 (2),放到数组的后面;如果剩下 (2),优先放一个 (two),其次一个 (four)(如果选这个,需要 one++
),其次是两个 (one),如果都不行就拆成两个 (1) 放到后面;如果剩下 (1),优先选一个 (one),其次一个 (two),其次一个 (four)(two++
),如果都不行就输出 (mathtt{NO})。
#include<bits/stdc++.h>
using namespace std;
inline void read(int &x)
{
x=0;int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
int four,two,one;
const int N=10000;
int a[N+10];
int main()
{
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=k;i++) scanf("%d",&a[i]);
four=n;two=n*2;
for(int i=1;i<=k;i++)
{
while(a[i]>=4)
{
if(four>=1)
four--;
else if(two>=2)
two-=2;
else
{
puts("NO");
return 0;
}
a[i]-=4;
}
}
sort(a+1,a+k+1,greater<int>());
// for(int i=1;i<=k;i++)
// if(a[i]>=4)
// {
// puts("NO");
// return 0;
// }
for(int i=1;i<=k;i++)
{
if(a[i]==3)
{
if(four>=1) four--;
else if(two>=2) two-=2;
else
{
a[++k]=2;
a[++k]=1;
}
}
else if(a[i]==2)
{
if(two>=1) two--;
else if(four>=1)
{
one++;
four--;
}
else if(one>=2)
one-=2;
else
{
a[++k]=1;
a[++k]=1;
}
}
else if(a[i]==1)
{
if(one>=1)
one--;
else if(two>=1)
two--;
else if(four>=1)
{
four--;
two++;
}
else
{
puts("NO");
return 0;
}
}
}
puts("YES");
return 0;
}
3.10 & 3.11
870E
特别鸣谢:cjx,glq。
将纵坐标或横坐标相同的点,将它们相连。此时图中形成若干个连通块,考虑每一个连通块,假设这个连通块有 (c_x) 个不同的 (x) 坐标,(c_y) 个不同的 (y) 坐标,若这个图有环,则给答案的贡献为 (2^{c_x+c_y});反之,若为数则贡献为 (2^{c_x+c_y}-1)。
让我们来感性理解一下下面的式子。考虑一个环,在平面的位置一定形如下面两种情况:
对于第一种情况,有这样一种方案:
也就是说,随便找若干条边都能够有一种构造方案使得这些边被选中,也就是 (2^{c_x+c_y}) 种情况。
第二种情况可以转化成第一种情况,所以略。
假设新来一个点,形如这样:
不难得出方案数还是 (2^{c_x+c_y}),只不过 (c_x,c_y) 变了。以上为有环的情况。
对于无环的情况,一定是下面两种情况:
不难得出,除了下面所有线都被选中的情况,其他所有情况都能被构造出,也就是 (2^{c_x+c_y}-1) 种情况。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
using namespace std;
const int N=1e5+10,M=4e5+10;
#define int long long
// void init(int n) {for(int i=1;i<=n;i++) f[i]=i;}
// int getf(int x) {return f[x]==x?x:f[x]]=getf(f[x]);}
struct node
{
int x,y,pos;
node(){}
}a[N+10],tmp[N+10];
bool cmp1(node x,node y) {return x.x<y.x;}
bool cmp2(node x,node y) {return x.y<y.y;}
int head[N],ver[M],nxt[M],tot=0;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
bool vis[N];
int cnt=0;
map<int,bool> xx,yy;
bool dfs(int x,int fa)
{
bool ans=0;
// cnt++;
xx[tmp[x].x]=1;
yy[tmp[x].y]=1;
vis[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(y==fa) continue;
if(vis[y])
{
ans=1;
continue;
}
if(dfs(y,x)) ans=1;
}
return ans;
}
const int MOD=1e9+7;
int qpow(int n)
{
int ans=1,base=2;
while(n)
{
if(n&1) ans*=base,ans%=MOD;
base*=base,base%=MOD;
// return
n>>=1;
}
return ans;
}
signed main()
{
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld %lld",&a[i].x,&a[i].y);
a[i].pos=i;
}
memcpy(tmp,a,sizeof(a));
sort(a+1,a+n+1,cmp1);
for(int i=1;i<n;i++)
if(a[i].x==a[i+1].x)
{
add(a[i].pos,a[i+1].pos);
add(a[i+1].pos,a[i].pos);
}
sort(a+1,a+n+1,cmp2);
for(int i=1;i<n;i++)
if(a[i].y==a[i+1].y)
{
add(a[i].pos,a[i+1].pos);
add(a[i+1].pos,a[i].pos);
}
int ans=1;
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
xx.clear();
yy.clear();
// cnt=0;
bool flag=dfs(i,0);
cnt=(int)xx.size()+(int)yy.size();
// printf("x+y:%lld
",(int)xx.size()+(int)yy.size());
if(flag)
ans*=qpow(cnt);
else
ans*=qpow(cnt)-1;
ans%=MOD;
}
}
printf("%lld",ans);
return 0;
}
962F
用到了点双的一个性质:每个点双内必有一个能涵盖点双里所有点的简单环。Tarjan 算法求一下所有的点双,判断一下每个点双是否有且仅有一个简单环,也就是是否满足 (|V|=|E|)。如果满足这个条件,那么就把所有的边加到答案里即可。
一个实现的细节:如果你存点双的方式是保存它们的点的话,那么暴力判断最坏情况下大概是 (mathcal O(nm)) 的,所以可以在 Tarjan 的时候直接存边,这样求出来的点双就是若干个边的形式。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<set>
using namespace std;
const int N=1e6+10,M=2e6+10;
int head[N],ver[M],nxt[M],tot=1;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int st[N],low[N],cnt=0,num=0,dfn[N],rt,top,st1[N],top1;
bool cut[N];
vector<int> dcc[N];
int col[N];
int c[N];
set<int> v[N],e[N];
void tarjan(int x,int fa)
{
dfn[x]=low[x]=++num;
int c=0;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(!dfn[y])
{
st[++top]=x;
st[++top]=y;
st[++top]=i/2;
// printf("i/2:%d
",i/2);
tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
c++;
if(x!=rt||c>1) cut[x]=1;
cnt++;
while(1)
{
int pos=st[top--],V=st[top--],U=st[top--];
v[cnt].insert(U);v[cnt].insert(V);
e[cnt].insert(pos);
if(x==U && V==y) break;
}
}
}
else if(dfn[y]<dfn[x] && y!=fa)
{
low[x]=min(low[x],dfn[y]);
st[++top]=x;
st[++top]=y;
st[++top]=i/2;
}
}
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
{
rt=i;
tarjan(i,0);
}
int ans=0;
set<int> Ans;
for(int i=1;i<=cnt;i++)
if(e[i].size()==v[i].size())
for(set<int>::iterator it=e[i].begin();it!=e[i].end();it++)
Ans.insert(*it);
printf("%d
",(int)Ans.size());
for(set<int>::iterator it=Ans.begin();it!=Ans.end();it++)
printf("%d ",*it);
return 0;
}
3.12
819B
从 (k) 到 (k+1),相当于把最后一个数挪到第一个数。记录几个值,( exttt{ftot,fcnt,ztot,zcnt}),分别记录 (p_i-ile 0) 的 (i-p_i) 的和,(p_i-ile 0) 的数量,(p_i-i>0) 的 (p_i-i) 的和,(p_i-i>0) 的数量。把所有可能的 (k) 枚举一遍,如果没有 (i<p_i) 变为 (ige p_i) 或者后者变为前者的情况,每次必定会有 ( exttt{ztot-=zcnt, ftot+=fcnt})。对于变化的情况,有下面两种:
- (i<p_i) 变为 (ige p_i),可以预处理出这些点变化的时间;
- (ige p_i) 变为 (i<p_i),这种情况发生当且仅当后面的挪到前面,特判一下即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=2e6;
int a[N+10],c[N+10];
signed main()
{
int n;
scanf("%lld",&n);
int ftot=0,fcnt=0,ztot=0,zcnt=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);//a[i]-i
if(a[i]<=i)
{
fcnt++;
ftot+=i-a[i];
}
else
{
zcnt++;
ztot+=a[i]-i;
c[a[i]-i]++;
}
}
int ans=ztot+ftot,k=0;
for(int i=1;i<n;i++)
{
int x=a[n-i+1];
ztot-=zcnt;
ftot+=fcnt;
zcnt-=c[i];
fcnt+=c[i];
ftot-=n-x;
ftot++;
fcnt--;
if(x==1) fcnt++;
else
{
c[x+(i-1)]++;
ztot+=(x-1);
zcnt++;
}
if(ftot+ztot<ans)
{
ans=ftot+ztot;
k=i;
}
}
printf("%lld %lld",ans,k);
return 0;
}
3.13
959D
由于是字典序,(i) 越小,(x_i) 也要尽量的小,但不能小过 (Y) 的字典序。由于要保证互质,所以可以开一个 multiset,维护在选了若干个 (x_i) 后还能选哪些数,在这里面二分(lower_bound()
)搜这一轮选那个数即可;每当选了一个数,就将其分解质因数并用类似埃氏筛的方式将这些质因数的倍数在 multiset 中全都 erase 掉。
分析一下复杂度。假设 (x_i) 的值域最大是 (m)(实现中开成 (2 imes 10^6) 可过),那么所有质数进行一次筛法时间复杂度的上界是 (mathcal O(m/1+m/2+cdots+m/m)=mathcal O(mln m))(由于质数数量只有 (pi(n)approx n/ln n) 那么多,所以远到不了这个上界),加上 erase 操作是 (mathcal O(mln mlog n)),分解质因数的时间复杂度是 (mathcal O(nsqrt{max{a_i}})),所以说总的时间复杂度是 (mathcal O(mln mlog n+nsqrt{max{a_i}}))。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6;
multiset<int> s;
bool f[N+10];
int a[N+10],n;
void bj(int x)
{//biao ji
for(int i=1;i*x<=N;i++)
{
f[i*x]=1;
if(s.find(i*x)!=s.end()) s.erase(i*x);
}
}
void divide(int n)
{
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
bj(i);
while(n%i==0) n/=i;
}
}
if(n>1)
{
bj(n);
}
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=N;i++) s.insert(i);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
f[1]=1;
divide(a[1]);
bool flag=1;
printf("%d ",a[1]);
for(int i=2;i<=n;i++)
{
if(flag)
{
multiset<int>::iterator it=s.lower_bound(a[i]);
printf("%d ",*it);
divide(*it);
if(*it>a[i]) flag=0;
}
else
{
multiset<int>::iterator it=s.lower_bound(2);
printf("%d ",*it);
divide(*it);
}
// divide(j);
// printf("%d ",j);
}
return 0;
}
949C
若 (x) 调整导致 (y) 必须调整,那么就在它们两个之间连一条边。形式化地,对于每个 ((x_i,y_i)),若 (u_{x_i}+1equiv u_{y_i}pmod h),则连一条 (x_i o y_i) 的边;若 (u_{y_i}+1equiv u_{x_i}pmod h),则连一条 (y_i o x_i) 的边。强连通分量缩个点,大小最小的、在新图中没有出度的强连通分量中的点就是答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5,M=2e5;
int head[N+10],ver[M+10],nxt[M+10],tot=0;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int low[N+10],dfn[N+10],num=0,sz[N+10],cnt=0,col[N+10];
int st[N+10],top=0;
bool vis[N+10];
void tarjan(int x)
{
low[x]=dfn[x]=++num;
vis[x]=1;
st[++top]=x;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
int y=0;++cnt;
do
{
vis[y]=0;
y=st[top--];
col[y]=cnt;
sz[cnt]++;
}while(x!=y);
}
}
int u[N+10],out[N+10];
int main()
{
int n,m,h;
scanf("%d%d%d",&n,&m,&h);
for(int i=1;i<=n;i++) scanf("%d",&u[i]);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
if((u[x]+1)%h==u[y]%h) add(x,y);
if((u[y]+1)%h==u[x]%h) add(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int x=1;x<=n;x++)
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(col[x]!=col[y])
out[col[x]]++;
}
int ans=0x7fffffff,f=0;
for(int i=1;i<=n;i++)
if(!out[col[i]] && sz[col[i]]<=ans)
{
ans=sz[col[i]];
f=col[i];
}
printf("%d
",ans);
for(int i=1;i<=n;i++)
if(col[i]==f)
printf("%d ",i);
return 0;
}
722D
把所有数扔到一个 set 中,执行若干次操作,每次找出 set 中的最大值 (t)(*--s.end()
),不断地 (tgetslfloor t/2
floor) 直到 set 中没有与其相等的元素(while(s.count(t)&&t) t/=2
),若 (t
ot= 0),就将 set 中原本的 (t) 修改为现在的 (t);反之,说明最大值无法更小,退出循环。最后 set 中的元素就是答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
int main()
{
set<int> s;
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
s.insert(x);
}
while(1)
{
int t=*--s.end(),tt=t;
while(s.count(t)&&t) t/=2;
if(t==0) break;
s.erase(tt);s.insert(t);
}
for(set<int>::iterator it=s.begin();it!=s.end();it++)
printf("%d ",*it);
return 0;
}
3.14
1066F
最优答案一定是类似这样的路径:
(f(i,0/1)) 表示走完 (max{x,y}=i)((i) 是离散化之后的值)的线,在左上((p_{i,0}))/右下((p_{i,1}))结束的最小答案,则有转移:
其中 (operatorname{dis}(x,y)) 表示 (x,y) 的曼哈顿距离。令 (M=max{m_i}),则 (min{f(M,0),f(M,1)}) 即为最终答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
#define int long long
const int N=2e5;
int x[N+10],y[N+10],t[N*2+10];
struct node
{
int x,y;
node(int xx,int yy){x=xx;y=yy;}
node(){}
bool operator<(const node &rhs)const{return y==rhs.y?x<rhs.x:y>rhs.y;}
};
vector<node> p[N+10];
int c[N+10],f[N+10][2];
int dis(node a,node b) {return abs(a.x-b.x)+abs(a.y-b.y);}
signed main()
{
int n,m=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld %lld",&x[i],&y[i]);
t[++m]=max(x[i],y[i]);
}
sort(t+1,t+m+1);
m=unique(t+1,t+m+1)-t-1;
for(int i=1;i<=n;i++)
p[lower_bound(t+1,t+m+1,max(x[i],y[i]))-t].push_back(node(x[i],y[i]));
p[0].push_back(node(0,0));
for(int i=1;i<=n;i++)
sort(p[i].begin(),p[i].end());
for(int i=1;i<=m;i++)
{
for(int j=1;(unsigned)j<p[i].size();j++)
c[i]+=dis(p[i][j],p[i][j-1]);
}
for(int i=1;i<=m;i++)
{
//0:begin, 1:end
vector<node>::iterator it0=p[i-1].begin(),it1=--p[i-1].end();
vector<node>::iterator now0=p[i].begin(),now1=--p[i].end();
f[i][0]=min(f[i-1][0]+dis(*it0,*now1)+c[i],f[i-1][1]+dis(*it1,*now1)+c[i]);
f[i][1]=min(f[i-1][0]+dis(*it0,*now0)+c[i],f[i-1][1]+dis(*it1,*now0)+c[i]);
}
printf("%lld
",min(f[m][0],f[m][1]));
}
// Hawking forever!
1481E
正难则反。令 (f(i)) 表示 (isim n) 最多不动多少个,(l_j,r_j) 分别表示颜色 (j) 的最左坐标、最右坐标,(c_{j}) 表示颜色 (j) 在 (isim n) 中的数量,则:
关于为什么当 (i ot= l_{a_i}) 时的转移不能是 (c_j+f(r_{a_i}+1)),可以看下面的样例:
input:
1 1 2 1 3
output:
2
correct f(i):
3 3 1 1 1
incorrect f(i):
4 4 3 2 1
如果转移了 (c_j+f(r_{a_i}+1)),相当于规定了 (f(r_{a_i}+1)) 这些数不能再移动,而这显然是不对的。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5e5;
int a[N+10],l[N+10],r[N+10],f[N+10],cnt[N+10];
void ckmx(int &x,int y) {x=max(x,y);}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(!l[a[i]]) l[a[i]]=i;
r[a[i]]=i;
}
for(int i=n;i;i--)
{
cnt[a[i]]++;
f[i]=f[i+1];
if(l[a[i]]==i) ckmx(f[i],cnt[a[i]]+f[r[a[i]]+1]);
else ckmx(f[i],cnt[a[i]]);
}
// for(int i=1;i<=n;i++) printf("%d ",f[i]);
printf("%d",n-f[1]);
return 0;
}
3.15
1027E
确定第一行和第一列后整个图形就确定下来了。对它们进行 dp,令 (f(i,j)) 表示前 (i) 行/列,最长同色串 (le j) 的数量,那么有转移 (f(i,j)gets f(i-k',min{i-k',j}))。利用前缀和的思想,确定前 (n) 行,最长同色串长度为 (j) 的数量 (g(j)=f(n,j)-f(n,j-1)),则答案为 (prodlimits_{i=1}^n prodlimits_{j=1}^n [ijle k]g(i)g(j))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=500;
int f[N+10][N+10],dp[N+10];
int main()
{
int n,K;
scanf("%d%d",&n,&K);
f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
for(int k=1;k<=j;k++)
{
f[i][j]+=f[i-k][min(i-k,j)];
f[i][j]%=998244353;
}
}
}
for(int i=1;i<=n;i++)
{
dp[i]=f[n][i]-f[n][i-1];
dp[i]+=998244353;dp[i]%=998244353;
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i*j<K)
{
ans+=(long long)dp[i]*dp[j]%998244353;
ans%=998244353;
}
printf("%d",ans*2%998244353);
return 0;
}
1029E
预处理出深度 (d_i),插到 priority_queue 里,每次取出深度最大的点, 并将其父亲及父亲的周围节点标记。
感性理解一下:每次能够取出来的点一定是叶子节点或者其后代已经处理完毕,此时这个点如果想要变合法有两种方式:将自己与根连边或将父亲与根连边。前者最多只能影响到两个点(自己,父亲),而后者则会影响到与自己同一父亲的其他节点,所以从贪心的角度来讲,每次选深度最大的节点的父亲不会使答案更劣。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=1e5;
int n,x,y,z;
//+1:x, -1:y, +1-1:z
int a[N+10];
int calc(int h)
{
int sum1=0,sum2=0;
for(int i=1;i<=n;i++)
{
if(a[i]>=h) sum1+=a[i]-h;
else sum2+=h-a[i];
}
if(sum1>sum2) return min(sum1*y+sum2*x,(sum1-sum2)*y+sum2*z);
else return min(sum1*y+sum2*x,(sum2-sum1)*x+sum1*z);
}
int bin3()
{
int l=0,r=1e9;
int ans=min(calc(l),calc(r));
while(l<=r)
{
int lmid=(l+r)/2,rmid=(lmid+r)/2;
int fl=calc(lmid),fr=calc(rmid);
if(fl>fr) l=lmid+1;
else r=rmid-1;
ans=min(ans,min(fl,fr));
}
return ans;
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
z=min(z,x+y);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
printf("%lld",bin3());
return 0;
}
1355E
先把 (M) 变成 (min{M,A+B})。考虑给一个 (H),代价是多少。令 (c_1=sumlimits_{i=1}^n [h_ige H](h_i-H),;c_2=sumlimits_{i=1}^n [h_i<H](H-h_i)),若 (c_1>c_2),答案为 (min{Bc_1+Ac_2,B(c_1-c_2)+Mc_2});若 (c_1le c_2),答案为 (min{Bc_1+Ac_2,A(c_2-c_1)+Mc_1})。这个东西是一个单谷的,所以可以三分 (H)。
提供一个整数三分的模板(https://www.cnblogs.com/--560/p/5242883.html):
ll bin3(int l,int r)
{
if(l>r) return -INF;
ll res=max(f(l),f(r));
while(l<=r){
int m=(l+r)>>1,mm=(m+r)>>1;
ll fm=f(m),fmm=f(mm);
if(fm<=fmm) l=m+1;
else r=mm-1;
res=max(res,max(fm,fmm));
}
return res;
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=1e5;
int n,x,y,z;
//+1:x, -1:y, +1-1:z
int a[N+10];
int calc(int h)
{
int sum1=0,sum2=0;
for(int i=1;i<=n;i++)
{
if(a[i]>=h) sum1+=a[i]-h;
else sum2+=h-a[i];
}
if(sum1>sum2) return min(sum1*y+sum2*x,(sum1-sum2)*y+sum2*z);
else return min(sum1*y+sum2*x,(sum2-sum1)*x+sum1*z);
}
int bin3()
{
int l=0,r=1e9;
int ans=min(calc(l),calc(r));
while(l<=r)
{
int lmid=(l+r)/2,rmid=(lmid+r)/2;
int fl=calc(lmid),fr=calc(rmid);
if(fl>fr) l=lmid+1;
else r=rmid-1;
ans=min(ans,min(fl,fr));
}
return ans;
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
z=min(z,x+y);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
printf("%lld",bin3());
return 0;
}
3.16
63D
答案一定是 YES
,因为可以这样填:
若 (b<d),(bmod 2=0) 则起点在 ((1,a+c)),(bmod 2=1) 则起点在 ((1,1));若 (bge d),(dmod 2=0) 则起点在 ((1,1)),(dmod 2=1) 则起点在 ((1,a+c))。从左到右、从右到左反复填即可,对于起点的讨论避免了死胡同的情况。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define mp make_pair
#define fi first
#define se second
int x[27];
char m[200][200];
int main()
{
int a,b,c,d,n;
scanf("%d %d %d %d %d",&a,&b,&c,&d,&n);
for(int i=1;i<=max(b,d);i++)
for(int j=1;j<=a+c;j++)
m[i][j]='.';
for(int i=1;i<=n;i++) scanf("%d",&x[i]);
pair<int,int> s;
bool right=0;
if(b>d)
{
if(d%2==0) s=mp(1,1),right=1;
else s=mp(1,a+c),right=0;
}
else
{
if(b%2==0) s=mp(1,a+c),right=0;
else s=mp(1,1),right=1;
}
int l=1,r=a+c;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=x[i];j++)
{
m[s.fi][s.se]=i+'a'-1;
if(right) s.se++;
else s.se--;
if(s.se>r)
{
s.fi++;
if(s.fi>min(b,d))
{
if(b>d) r=a;
else l=a+1;
}
s.se=r;
right^=1;
}
else if(s.se<l)
{
s.fi++;
if(s.fi>min(b,d))
{
if(b>d) r=a;
else l=a+1;
}
s.se=l;
right^=1;
}
}
}
puts("YES");
for(int i=1;i<=max(b,d);i++)
{
for(int j=1;j<=a+c;j++)
putchar(m[i][j]);
putchar('
');
}
return 0;
}
67D
记值 (i) 在 (y) 中的位置记为 (p_i),同时令 (c_i=p_{a_i}),(c) 的最长下降子序列就是答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline void read(int &x)
{
x=0;;int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
const int N=1e6+10;
struct seg
{
int l,r,val;
seg(){}
}t[N<<2];
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
t[p].val=0;
if(l==r) return;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
int query(int p,int l,int r)
{
if(l<=t[p].l&&t[p].r<=r) return t[p].val;
int ans=0,mid=(t[p].l+t[p].r)/2;
if(l<=mid) ans=max(ans,query(p*2,l,r));
if(r>mid) ans=max(ans,query(p*2+1,l,r));
return ans;
}
void modify(int p,int l,int d)
{
if(t[p].l==t[p].r)
{
t[p].val=d;
return;
}
int mid=(t[p].l+t[p].r)/2;
if(l<=mid) modify(p*2,l,d);
else modify(p*2+1,l,d);
t[p].val=max(t[p*2].val,t[p*2+1].val);
}
int a[N],b[N],pos[N],f[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
pos[b[i]]=i;
}
for(int i=1;i<=n;i++) a[i]=pos[a[i]];
build(1,1,n);
int ans=0;
for(int i=1;i<=n;i++)
{
f[i]=max(1,query(1,a[i]+1,n)+1);
modify(1,a[i],f[i]);
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}
69D
dfs。bool dfs(x, y)
表示搜到了 ((x,y)),执棋者是否能赢。若 dfs(x + dx[i], y + dy[i])
有一个返回的是 (0),则 dfs(x, y)
直接返回 (1)。记忆化一下,复杂度是 (mathcal O(d^2n))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int vis[1010][1010],dx[30],dy[30],n,d;
int dfs(int x,int y)
{
if(~vis[x][y]) return vis[x][y];
if((x-200)*(x-200)+(y-200)*(y-200)>d*d) return vis[x][y]=1;
for(int i=1;i<=n;i++)
if(dfs(x+dx[i],y+dy[i])==0)
return vis[x][y]=1;
return vis[x][y]=0;
}
int main()
{
memset(vis,-1,sizeof(vis));
int x,y;
scanf("%d %d %d %d",&x,&y,&n,&d);
for(int i=1;i<=n;i++)
scanf("%d %d",&dx[i],&dy[i]);
if(dfs(x+200,y+200)==1)
printf("Anton");
else printf("Dasha");
return 0;
}
3.18
914D
一个区间能够满足条件,当且仅当这个区间的 gcd 是 (x) 的倍数或者存在一个分界点使得分界点左边、右边的 gcd 都是 (d) 的倍数。线段树维护区间 gcd,树上二分计算这个分界点即可。时间复杂度 (mathcal O((n+m) log^2 n))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gcd(int x,int y) {return y==0?x:gcd(y,x%y);}
const int N=5e5+10;
int a[N];
struct seg
{int l,r,g;}t[N<<2];
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
if(l==r)
{
t[p].g=a[l];
return;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].g=gcd(t[p*2].g,t[p*2+1].g);
}
void modify(int p,int x,int d)
{
if(t[p].l==t[p].r)
{
t[p].g=d;
return;
}
int mid=(t[p].l+t[p].r)/2;
if(x<=mid) modify(p*2,x,d);
else modify(p*2+1,x,d);
t[p].g=gcd(t[p*2].g,t[p*2+1].g);
}
int d,pos=0;
void Query(int p)
{
if(t[p].l==t[p].r)
{
if(!pos) pos=t[p].l;
return;
}
if(t[p*2].g%d) Query(p*2);
else Query(p*2+1);
}
void query1(int p,int l,int r)
{
if(l<=t[p].l&&t[p].r<=r)
{
if(t[p].g%d) Query(p);
return;
}
int mid=(t[p].l+t[p].r)/2;
if(l<=mid) query1(p*2,l,r);
if(r>mid) query1(p*2+1,l,r);
}
int query(int p,int l,int r)
{
if(l<=t[p].l&&t[p].r<=r) return t[p].g;
int ans=0,mid=(t[p].l+t[p].r)/2;
if(l<=mid) ans=gcd(query(p*2,l,r),ans);
if(r>mid) ans=gcd(query(p*2+1,l,r),ans);
return ans;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
int m;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
// printf("i=%d
",i);
int p;
scanf("%d",&p);
if(p==2)
{
int x,d;
scanf("%d%d",&x,&d);
modify(1,x,d);
a[x]=d;
}
else
{
int l,r;
scanf("%d%d%d",&l,&r,&d);
if(l==r)
{
puts("YES");
continue;
}
if(query(1,l,r)%d==0)
{
puts("YES");
continue;
}
pos=0;
query1(1,l,r);
if(pos==l)
{
if(query(1,l+1,r)%d) puts("NO");
else puts("YES");
continue;
}
if(pos==r)
{
if(query(1,l,r-1)%d) puts("NO");
else puts("YES");
continue;
}
if(query(1,l,pos-1)%d==0 && query(1,pos+1,r)%d==0) puts("YES");
else puts("NO");
}
}
return 0;
}
935D
令 (f(i,0/1)) 表示考虑前 (i) 位,前 (i) 位里 (A=B)、(A>B) 的数量,暴力转移即可。最终答案为 (dfrac{f(n,1)}{m^k}),其中 (k) 表示 (A,B) 中 (0) 的数量。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=1e5+10,MOD=1e9+7;
int a[N],b[N],f[N][2];
int qpow(int x,int n)
{
x%=MOD;
int ans=1;
while(n)
{
if(n&1) ans=ans*x%MOD;
x=x*x%MOD;
n>>=1;
}
return ans;
}
signed main()
{
int n,m;
scanf("%lld%lld",&n,&m);
int cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
cnt+=(a[i]==0);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&b[i]);
cnt+=(b[i]==0);
}
f[0][0]=1;
for(int i=1;i<=n;i++)
{
if(a[i]&&b[i])
{
if(a[i]==b[i])
{
f[i][0]=f[i-1][0];
f[i][1]=f[i-1][1];
}
else if(a[i]>b[i])
f[i][1]=f[i-1][0]+f[i-1][1];
else if(a[i]<b[i])
f[i][1]=f[i-1][1];
f[i][0]%=MOD;
f[i][1]%=MOD;
}
else if(a[i]&&!b[i])
{
f[i][1]=f[i-1][1]*m%MOD+f[i-1][0]*(a[i]-1)%MOD;
f[i][1]%=MOD;
f[i][0]=f[i-1][0];
}
else if(!a[i]&&b[i])
{
f[i][1]=f[i-1][1]*m%MOD+f[i-1][0]*(m-b[i])%MOD;
f[i][1]%=MOD;
f[i][0]=f[i-1][0];
}
else if(!a[i]&&!b[i])
{
f[i][0]=f[i-1][0]*m%MOD;
f[i][0]%=MOD;
f[i][1]=f[i-1][0]*(m-1)%MOD*m%MOD*500000004ll%MOD
+ f[i-1][1]*m%MOD*m%MOD;
}
}
// puts("f[i][0]:");
// for(int i=1;i<=n;i++) printf("%lld ",f[i][0]);
// puts("");
int di=qpow(qpow(m,cnt),MOD-2);
printf("%lld",f[n][1]*di%MOD);
return 0;
}
930C
考虑什么情况下会存在一个点使得这个点被所有区间覆盖,可以得出,若令 (f(x)) 表示 (x) 的覆盖次数,那么当 (x) 单峰或单调的时候存在一个点使得这个点被所有区间覆盖,于是这题就变成了合唱队列加强版,线段树 (mathcal O(n log n)) 直接转移即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100000+10;
struct segmenttree
{
struct seg
{
int l,r,val;
seg(){l=r=val=0;}
}t[N<<2];
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
if(l==r) return;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void modify(int p,int x,int d)
{
if(t[p].l==t[p].r)
{
t[p].val=d;
return;
}
int mid=(t[p].l+t[p].r)/2;
if(x<=mid) modify(p*2,x,d);
else modify(p*2+1,x,d);
t[p].val=max(t[p*2].val,t[p*2+1].val);
}
int query(int p,int l,int r)
{
if(l<=t[p].l && t[p].r<=r) return t[p].val;
int mid=(t[p].l+t[p].r)/2,ans=0;
if(l<=mid) ans=max(ans,query(p*2,l,r));
if(r>mid) ans=max(ans,query(p*2+1,l,r));
return ans;
}
}a,b;
int c[N],d[N];
int f1[N],f2[N];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
int l,r;
scanf("%d %d",&l,&r);
c[l]++;
c[r+1]--;
}
int sum=0;
for(int i=1;i<=m;i++)
{
sum+=c[i];
// printf("%d ",sum);
d[i]=sum;
}
a.build(1,0,n);
b.build(1,0,n);
for(int i=1;i<=m;i++)
{
f1[i]=a.query(1,0,d[i])+1;
a.modify(1,d[i],f1[i]);
// printf("%d ",f1[i]);
}
for(int i=m;i;i--)
{
f2[i]=b.query(1,0,d[i])+1;
b.modify(1,d[i],f2[i]);
// printf("%d ",f2[i]);
}
int ans=0;
for(int i=0;i<=m;i++) ans=max(ans,f1[i]+f2[i+1]);
printf("%d",ans);
return 0;
}
3.19 ~ 3.31
博客园维护,所以写到了本地,下载链接。