Atcoder 选做
[ARC103F] Distance Sums
首先显然 \(D_i\) 的最小值被重心取到,不妨以重心为根。
对于一条边连接的两个点 \(x,y\) ,不妨设这条边 \(x\) 侧的点数为 \(siz_x\) ,\(y\) 侧为 \(siz_y\) 。
那么 \(D_y=D_x+siz_x-siz_y=D_x+siz_x-(n-siz_x)=D_x+2\times siz_x -n\) 。
那么我们就可以知道以重心为根的时候,从 \(fa_x\) 转移到 \(x\) 的时候,\(2\times siz_x \leq n\),因此沿着叶向,\(D_i\) 递减。
那我们可以直接剥叶子,把 \(D_i\) 从大到小排序,然后依次确定父亲。
最后检查跟合不合法就好了。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
map<LL,int> node;
int n;
const int N = 1e5+7;
LL D[N];
int siz[N],fa[N],dep[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld",&D[i]);
node[D[i]]=i;
}
sort(D+1,D+n+1);
for(int i=n;i>=2;i--)
{
int x=node[D[i]];
siz[x]++;
fa[x]=node[D[i]+2ll*siz[x]-n];
if(!fa[x]||fa[fa[x]])
{
cout<<-1;
return 0;
}
siz[fa[x]]+=siz[x];
}
LL S=0;
for(int i=2;i<=n;i++)
{
int x=node[D[i]];
dep[x]=dep[fa[x]]+1;
S+=dep[x];
}
if(S!=D[1])
{
cout<<-1;
return 0;
}
for(int i=2;i<=n;i++)
{
int x=node[D[i]];
printf("%d %d\n",x,fa[x]);
}
return 0;
}
[ARC102F] Revenge of BBuBBBlesort!
如果在 \(i\) 位置做过交换,则称 \(i\) 为一个中心点。
性质1:若 \(i\) 为中心点,则在原始排列 \(p\) 中 \(p_i = i\)
\(\texttt{proof:}\)
对 \(i\) 进行过操作后 , 有 \(p_{i-1}<p_i<p_{i+1}\),不妨设现在\(p_{i}<i\),那么一定会在 \(p_{i-1}\) 进行一次交换。
也就是说,我们需要使得 \(p_{i-2}>p_{i-1}>p_i\),这需要在 \(p_{i-2}\) 进行一次交换,也就是说交换完变成 \(p_{i-3}<p_i<p_{i-2}<p_{i-1}\),那就无法操作了。
另一侧同理。
设 \(a_i=[p_i=i]\),那么我们只会对 \(a_i=1\) 的位置进行操作。
我们可以发现,如果 \(a_i=1,a_{i+1}=1\),那么在 \(i,i+1\) 都不会进行操作。
那么 我们可以把 \(a\) 换分成若干段,使得每个段内没有连续相同的\(a\)。
那么各个段是独立的。
合法的条件:
-
[l,r] 内的数恰好覆盖 [l,r]
-
把\(a_i=0\)的单独拿出来,如果这个序列中有长度大于2的下降子序列则不合法。
\(\texttt{proof:}\)
不妨设 \(p_i>p_j>p_k,i<j<k\)。
如果我们现在先交换 \(p_i,p_j\),那么一定存在 \(a_x=1,i<x<j,p_j<p_x<p_i\)
然后再交换 \(p_i,p_k\),那么一定存在 \(a_y=1,j<y<k,p_k<p_y<p_i\)。
现在我们要交换\(p_j,p_k\),但是因为 现在 \(p_j<p_x\),因此比不可能交换。
其他方案同理。
复杂度 \(O(n)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
int n,p[N],a[N];
void badending()
{
printf("No\n");
exit(0);
}
int mx[N];
void check(int l,int r)
{
int Max=0,SMax=0;
for(int i=l;i<=r;i++)
{
if(p[i]<l||p[i]>r)badending();
if(a[i]==0)
{
mx[i]=Max;
Max=max(Max,p[i]);
}
}
int Min=1e9;
for(int i=r;i>=l;i--)
{
if(a[i]==0)
{
if(mx[i]>p[i]&&p[i]>Min)badending();
Min=min(Min,p[i]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&p[i]);
a[i]=(p[i]==i);
}
for(int i=1;i+2<=n;i++)
if(a[i]==0&&a[i+1]==0&&a[i+2]==0)badending();
for(int i=1;i<=n;i++)
{
int j=i;
while(j+1<=n&&a[j]!=a[j+1])j++;
check(i,j);
i=j;
}
printf("Yes\n");
return 0;
}
[ARC111F] Do you like query problems?
首先为了方便,转成期望。
根据期望的线性性,答案等于每个位置的贡献之和。
记 \(a_{t,i}\) 为 \(t\) 次操作后 \(i\) 位置的数,总的操作种类数 \(S=\frac{n(n+1)}{2}\times (m+m+1)\),\(Q(i)=\frac{i(n-i+1)}{S}\)为一个当前操作是一个询问操作且包含了位置 \(i\)的概率,那么答案为:
根据整数概率公式,\(E(a_{t,i})=\sum_{w=1}P(a_{t,i}\ge w)\) 。
对于每个 \(w\) 独立计算,把大于等于 \(v\) 的数看成1,小于的看成0,那么最终就是 \(a_{t,i}=1\) 的概率。
设可能会导致 \(a_i\) 改变的操作为关键操作,那么就有两种,一种是 \(v\geq w\) 的取 \(\max\) 操作,一种是\(v<w\) 的取 $\min $ 操作,概率记为\(p1(i)=\frac{i(n-i+1)(m-w)}{S},p2(i)=\frac{i(n-i+1)w}{S}\)。
那么 \(t\) 次操作后 \(a_i=1\) 就是要求至少有一个关键操作且最后一次操作是 \(p1(i)\),这个概率 \(p_{t,i,w}=\frac{p1(i)}{p1(i)+p2(i)}(1-(1-p1(i)-p2(i))^t)\) 。
记 \(s_{t,i}=\sum\limits_{w=1}^{m-1}p_{t,i,w}=\sum\limits_{w=1}^{m-1}\frac{m-w}{m}(1-(1-\frac{i(n-i+1)m}{S})^t)=\frac{m-1}{2}(1-(1-\frac{i(n-i+1)m}{S})^t)\)
枚举 \(i\),后面就是一个等比数列求和,复杂度\(O(n\log n)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1000;
const int mod = 998244353;
const int inv2=(mod+1)/2;
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
int n,m,q;
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int inv(int a){return Pow(a,mod-2);}
int Sum(int a,int b)
{
if(a==1) return b;
return mul((Pow(a,b)-1+mod)%mod,inv(a-1));
}
int main()
{
cin>>n>>m>>q;
int S=mul(n,mul(n+1,mul(inv2,m+m+1)));
int ans=0;
for(int i=1;i<=n;i++)
{
int Q=mul(mul(i,n-i+1),inv(S));
int P=mul(m-1,inv2);
int A=mul(mul(i,n-i+1),mul(m,inv(S)));
int W=Sum((1-A+mod)%mod,q);
ans=(ans+mul(P,mul(Q,(q-W+mod)%mod)))%mod;
}
cout<<mul(ans,Pow(S,q));
return 0;
}
[ARC131F] ARC Stamp
考虑倒序操作,将被操作过的位置设为 \(?\) ,那么可以被操作的有如下几种:\(\texttt{ARC,AR?,?RC,A?C,A??,?R?,??C}\) 。
考虑将序列划分成若干段,每段是上述的一种,那么每段是独立的,把所有被操作过的段的方案数。
因为每一段是否被选择和他前后两端都有关系,因此不妨每三个计算一次贡献。
考虑 \(dp\) ,\(f_{i,x,y}\) 表示前 \(i\) 段,第 \(i-1\) 段的状态是 \(x\),第 \(i\) 段的状态是 \(y\) 的方案数,转移时枚举下一段的状态。
复杂度 \(O(n^2)\)
code
#include<bits/stdc++.h>
using namespace std;
int n,k;
const int N = 5040;
char S[N];
int a[N],b[N];
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
PII range[N];
int m=0;
int dp[N][N][2][2];
int bef[N][N];
const int mod = 998244353;
int w[N];
int calc(int i,int x,int y,int z)
{
if(!i)return 1;
int A=range[i-1].second,B=range[i].first;
int C=range[i].second,D=range[i+1].first;
bool must1=0;
bool must0=0;
if(x==1&&bef[A][B]==2)must1=1;
if(z==1&&bef[C][D]==1)must1=1;
if(x==0&&bef[A][B]==1)must0=1;
if(z==0&&bef[C][D]==2)must0=1;
if(must0&&must1)return 0;
if(must0) return y==0;
if(must1) return (y==1)*w[i];
if(y==0) return 1;
return w[i]-1;
}
int main()
{
scanf("%s",S+1);
n=strlen(S+1);
cin>>k;k=min(k,n);
for(int i=1;i<=n;i++)
if(S[i]=='A')a[i]=1;
else if(S[i]=='R')a[i]=2;
else a[i]=3;
for(int i=1;i<=n;i++)b[i]=a[i];
while(1)
{
bool flag=0;
for(int i=1;i+2<=n;i++)
{
if(b[i]==1&&b[i+1]==2&&b[i+2]==3)
{
range[++m]=mk(i,i+2);
b[i]=b[i+1]=b[i+2]=0;
flag=1;
continue;
}
if(b[i]==0&&b[i+1]==2&&b[i+2]==3)
{
range[++m]=mk(i+1,i+2);
bef[i][i+1]=1;
b[i]=b[i+1]=b[i+2]=0;
flag=1;
continue;
}
if(b[i]==1&&b[i+1]==2&&b[i+2]==0)
{
range[++m]=mk(i,i+1);
bef[i+1][i+2]=2;
b[i]=b[i+1]=b[i+2]=0;
flag=1;
continue;
}
if(b[i]==1&&b[i+1]==0&&b[i+2]==0)
{
range[++m]=mk(i,i);
bef[i][i+1]=2;
b[i]=b[i+1]=b[i+2]=0;
flag=1;
continue;
}
if(b[i]==0&&b[i+1]==2&&b[i+2]==0)
{
bef[i][i+1]=1;
bef[i+1][i+2]=2;
range[++m]=mk(i+1,i+1);
b[i]=b[i+1]=b[i+2]=0;
flag=1;
continue;
}
if(b[i]==0&&b[i+1]==0&&b[i+2]==3)
{
bef[i+1][i+2]=1;
range[++m]=mk(i+2,i+2);
b[i]=b[i+1]=b[i+2]=0;
flag=1;
continue;
}
}
if(!flag)break;
}
sort(range+1,range+m+1);
for(int i=1;i<=m;i++)
{
w[i]=1;
for(int u=range[i].first;u<=range[i].second;u++)w[i]=w[i]*3;
}
dp[0][0][0][0]=1;
for(int i=1;i<=m;i++)
for(int j=0;j<=min(i-1,k);j++)
for(int x=0;x<=1;x++)
for(int y=0;y<=1;y++)
if(dp[i-1][j][x][y])
{
for(int z=0;z<=1;z++)
{
dp[i][j+z][y][z]=(dp[i][j+z][y][z]+1ll*dp[i-1][j][x][y]*calc(i-1,x,y,z)%mod)%mod;
}
}
int res=0;
for(int i=0;i<=k;i++)
for(int c=0;c<=1;c++)
for(int d=0;d<=1;d++)
res=(res+1ll*dp[m][i][c][d]*calc(m,c,d,0)%mod)%mod;
cout<<res;
return 0;
}
[ARC085F] NRE
简单题,设 \(dp_{i,j}\) 表示前 \(i\) 个位置,覆盖最远的区间覆盖到 \(j\) 的最小代价,转移是简单的。
那么只需要线段树优化 \(dp\) 即可。
code
// LUOGU_RID: 111038731
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
typedef long long LL;
void ckmax(int &x,int v){x=max(x,v);}
int tag[N*4],mn[N*4],upd[N*4];
const int INF = 1e8;
void pushtag(int k,int v)
{
mn[k]+=v;
tag[k]+=v;
upd[k]+=v;
}
void pushmin(int k,int v)
{
mn[k]=min(mn[k],v);
upd[k]=min(upd[k],v);
}
void pushdown(int k)
{
if(tag[k])
{
pushtag(k<<1,tag[k]);
pushtag(k<<1|1,tag[k]);
tag[k]=0;
}
if(upd[k]<=INF)
{
pushmin(k<<1,upd[k]);
pushmin(k<<1|1,upd[k]);
upd[k]=INF;
}
}
void pushup(int k)
{
mn[k]=min(mn[k<<1],mn[k<<1|1]);
}
void Add(int k,int l,int r,int L,int R,int v)
{
if(L<=l&&r<=R)
{
pushtag(k,v);
return;
}
pushdown(k);
int mid=(l+r)>>1;
if(L<=mid)Add(k<<1,l,mid,L,R,v);
if(R>mid)Add(k<<1|1,mid+1,r,L,R,v);
pushup(k);
}
void Min(int k,int l,int r,int L,int R,int v)
{
if(L<=l&&r<=R)
{
pushmin(k,v);
return;
}
pushdown(k);
int mid=(l+r)>>1;
if(L<=mid)Min(k<<1,l,mid,L,R,v);
if(R>mid)Min(k<<1|1,mid+1,r,L,R,v);
pushup(k);
}
int Qry(int k,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return mn[k];
pushdown(k);
int res=INF;
int mid=(l+r)>>1;
if(L<=mid)res=min(res,Qry(k<<1,l,mid,L,R));
if(R>mid)res=min(res,Qry(k<<1|1,mid+1,r,L,R));
return res;
}
void Build(int k,int l,int r)
{
mn[k]=INF;
upd[k]=INF;
tag[k]=0;
if(l==r)return;
int mid=(l+r)>>1;
Build(k<<1,l,mid);
Build(k<<1|1,mid+1,r);
}
int n,w[N],m,dp[N];
void ADD(int l,int r,int v)
{
Add(1,0,n,l,r,v);
}
void MIN(int l,int r,int v)
{
Min(1,0,n,l,r,v);
}
int QRY(int l,int r)
{
return Qry(1,0,n,l,r);
}
vector<int> G[N];
int main()
{
// freopen("a.in","r",stdin);
// freopen("a.ans","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
cin>>m;
for(int i=1;i<=m;i++)
{
int l,r;
scanf("%d %d",&l,&r);
G[l].push_back(r);
}
Build(1,0,n);
MIN(0,0,0);
for(int i=1;i<=n;i++)
{
for(auto x:G[i])MIN(x,x,QRY(0,x-1));
//a[i]=0
if(w[i]==1)ADD(0,i-1,1);
//a[i]=1
if(w[i]==0)ADD(i,n,1);
}
cout<<QRY(0,n);
return 0;
}
[ARC094F] Normalization
可以发现如果把 \(a,b,c\) 设为 \(0,1,2\),那么操作不会改变字符串中数字总和 \(\%3\) 意义下的数。
首先特判掉 \(S\) 都是一个字符以及 \(|S|<=3\) 的情况。
那么一个串 \(T\) 可以被得到的必要条件是: \(T\) 中存在一对相邻且相等的位置,且两个序列 \(\%3\) 相等。
猜一下这个也是充分的,事实也确实如此。
因此直接 \(dp\) 就好了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
const int mod = 998244353;
char s[N];
int n;
int dp[N][3][2][3];
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
bool flag=1,flag2=0;
for(int i=1;i<n;i++)
{
flag&=(s[i]==s[i+1]);
flag2|=(s[i]==s[i+1]);
}
if(flag)
{
printf("1\n");
return 0;
}
if(n==2)
{
cout<<2;
return 0;
}
if(n==3)
{
if(s[1]!=s[2]&&s[2]!=s[3]&&s[1]!=s[3])cout<<3;
else if(s[1]==s[2])cout<<6;
else if(s[2]==s[3])cout<<6;
else cout<<7;
return 0;
}
for(int c=0;c<=2;c++)dp[1][c][0][c]=1;
int w=0;
for(int i=1;i<=n;i++)w=(w+s[i]-'a')%3;
for(int i=2;i<=n;i++)
{
for(int v=0;v<=2;v++)
for(int c=0;c<=1;c++)
for(int a=0;a<=2;a++)
if(dp[i-1][v][c][a])
{
for(int b=0;b<=2;b++)
dp[i][(v+b)%3][c|(a==b)][b]=(dp[i][(v+b)%3][c|(a==b)][b]+dp[i-1][v][c][a])%mod;
}
}
int ans=0;
for(int c=0;c<=2;c++)ans=(ans+dp[n][w][1][c])%mod;
cout<<(ans+1-flag2)%mod;
return 0;
}
[ARC082F] Sandglass
设 \(f_{a}(t)\) 为初始值为 \(a\) 的情况下,时间 \(t\) 时的值。
那么整个图像就是一条折线,碰到 \(y=0\) 和 \(y=X\) 就会顶着,而遇到 \(x=r_i\) 就会改变斜率。
容易发现对于 \(0\leq a\leq b\leq X,t\in Z,f_a(t)\leq f_b(t)\) 。
同时,如果在某一时刻 \(t\),\(f_a(t)=f_0(t)\) 或者 \(f_a(t)=f_X(t)\),那么之后,\(f_a(t)\) 就会一直和 \(f_0(t)\) 或 \(f_X(t)\) 相等,而如果从来没有,那么说明\(f_a(t)\)一直没有触碰上下界,因此当前位置可以直接求出,设为 \(x\)。
那么如果 \(x<f_0(t)\),最终就是 \(f_0(t)\),如果 \(>f_X(t)\),最终就是 \(f_X(t)\),否则就是 \(x\) 。
复杂度 \(O(n+m)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
LL X,O,n,q,fo,fx,f,p[N];
int main()
{
scanf("%lld",&X);
cin>>n;
for(int i=1;i<=n;i++)scanf("%lld",&p[i]);
fo=O;fx=X;f=0;
int now=0;
cin>>q;
while(q--)
{
LL t,a;
scanf("%lld %lld",&t,&a);
while(now+1<=n&&p[now+1]<=t)
{
if(now&1)
{
fx=min(X,fx+p[now+1]-p[now]);
fo=min(X,fo+p[now+1]-p[now]);
f+=p[now+1]-p[now];
}
else
{
fx=max(O,fx-p[now+1]+p[now]);
fo=max(O,fo-p[now+1]+p[now]);
f-=p[now+1]-p[now];
}
now++;
}
LL Fx=0,Fo=0,Fa=0;
if(now&1)
{
Fx=min(X,fx+t-p[now]);
Fo=min(X,fo+t-p[now]);
Fa=f+t-p[now];
}
else
{
Fx=max(O,fx-t+p[now]);
Fo=max(O,fo-t+p[now]);
Fa=f-t+p[now];
}
Fa+=a;
Fa=min(Fa,Fx);
Fa=max(Fa,Fo);
printf("%lld\n",Fa);
}
return 0;
}
[ARC115F] Migration
考虑二分答案,对于两个可以互相到达的状态,在这两个状态之间连一条边,那么就是询问 \(S,T\) 两个状态是否在同一个联通快内。
这不好直接判断,考虑求出 \(S,T\) 可以到达的,\(\sum a_i\) 最小的状态(相等比较字典序),如果相等说明,\(S,T\)在同一个联通块里。
对于每个点 \(x\),求出 \(y=f_x\) ,满足 \(h_y<h_x\) 且 \(x\to y\) 上最大的点权最小 。
这样,我们每次如果满足限制就\(s_i\to f_{s_i},t_i\to f_{t_i}\),因为点权和减少所以一定是不劣的。
用堆维护,每次取出最小值即可。
考虑去掉二分,实际上每次取出 \(S,T\) 里较小的一个更新即可。
复杂度 \(O(n^2+nk\log k)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 2020;
typedef long long LL;
struct edge
{
int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
int n,k;
int h[N];
bool cmp(int x,int y)
{
if(h[x]==h[y])return x<y;
return h[x]<h[y];
}
int Min(int x,int y)
{
if(h[x]==h[y]) return min(x,y);
if(h[x]<h[y]) return x;
return y;
}
int w[N],f[N],val[N];
void dfs(int x,int pre)
{
val[x]=h[x];
if(pre)val[x]=max(val[x],val[pre]);
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==pre)continue;
dfs(y,x);
}
}
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
priority_queue<PII,vector<PII>,greater<PII> >A,B;
int a[N],b[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&h[i]);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
for(int x=1;x<=n;x++)
{
dfs(x,0);
for(int y=1;y<=n;y++)
if(cmp(y,x))
{
if(!f[x]||val[y]-h[x]<w[x])
{
f[x]=y;
w[x]=val[y]-h[x];
}
}
}
int K;
scanf("%d",&K);
LL S=0,T=0,C=0;
for(int i=1;i<=K;i++)
{
scanf("%d %d",&a[i],&b[i]);
S+=h[a[i]];T+=h[b[i]];
if(f[a[i]])A.push(mk(w[a[i]],i));
if(f[b[i]])B.push(mk(w[b[i]],i));
C+=(a[i]!=b[i]);
}
LL ans=max(S,T);
while(C)
{
if(B.empty()||(!A.empty()&&X(A.top())<X(B.top())))
{
int i=A.top().second;A.pop();
ans=max(ans,S+w[a[i]]);
C-=(a[i]!=b[i]);S-=h[a[i]];
a[i]=f[a[i]];
C+=(a[i]!=b[i]);S+=h[a[i]];
if(f[a[i]])A.push(mk(w[a[i]],i));
}
else
{
int i=B.top().second;B.pop();
ans=max(ans,T+w[b[i]]);
C-=(a[i]!=b[i]);T-=h[b[i]];
b[i]=f[b[i]];
C+=(a[i]!=b[i]);T+=h[b[i]];
if(f[b[i]])B.push(mk(w[b[i]],i));
}
}
cout<<ans;
return 0;
}
[ARC106F] Figures
和度数有关,考虑 \(prufer\) 序列。
如果 \(i\) 里面选了 \(a_i\) 个洞,那么最终 \(i\) 的度数就是 \(a_i\),最终在 \(prufer\) 序列里就会出现 \(a_i-1\) 次,同时这 \(a_i\) 个洞的匹配是有序匹配,所以方案数也要乘上 \(a_i!\) 。
显然所有点的度数的和就是 \(2(n-1)\)
设 \(F_i(x)\) 为第 \(i\) 个点的生成函数, 那么答案就是\(ans=(n-2)!\prod\limits_{i=1}^nF_i(x)\)。
考虑吸收恒等式,
记 \(S=\sum d_i\)
可以 \(O(n)\) 计算了。
code
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int n;
int main()
{
cin>>n;
int mul=1,sum=0;
for(int i=1;i<=n;i++)
{
int d;
scanf("%d",&d);
mul=1ll*mul*d%mod;
sum=(sum+d)%mod;
}
for(int i=0;i<=n-3;i++)mul=1ll*mul*(sum-n-i+mod)%mod;
cout<<mul;
return 0;
}
[ARC127F] ±AB
- \(A+B-1\leq M\)
引理一:若 \(x=V+pA+qB\in [0,M]\) ,则 \(V\) 可以变成 \(x\) 。
若 \(p,q\) 同号,显然成立。
若不同号,不妨设 \(p>0,q<0\),那么我们可以一直减 \(B\) 直到达到上限或者不能减了,若达到上限,此时一定可以加 \(p\) 次 \(A\),若不能减了,当前的数字 \(v<B\),那么一定可以执行一次加 \(A\),因为 \(v+A\leq A+B-1\leq M\),因此我们就不停地 \(-B,+A\) 直到合法即可。
因为 \(\gcd(A,B)=1\),所以根据裴蜀定理,\(x\) 可以取遍 \([0,M]\) 。
- \(A+B-1>M\)
不妨令 \(V=V\bmod A\),显然这和原问题等价。
显然第一步不能选择 \(-A\),且因为 \(A<B\),第一步也不能选择 \(-B\) 。
不妨设第一次选择了 \(+A\),那么接下来不能选择 \(-A\),因为重复了,也不能 \(+B\),因为 \(A+B-1>M\)。那么只能在 \(+A,-B\) 中选一个,并且显然两个不能同时选,因此对于每个数,都只有唯一的出边,另一种走法 \(+B,-A\) 同理。
引理二:转移图无环
不妨设最小环走了 \(x\) 次 \(+A\),\(y\) 次 \(-B\),则 \(Ax=By\),因为 \(\gcd(A,B)=1\),该方程最小正整数解为 \(B,A\)。
但是因为 \(A+B>M+1\),所以环一定重复经过了某些点,与环是最小的矛盾。
引理三:两种走法无交
因为两种走法起点相同,所以若相交,那么一定有环,与上矛盾。
因此可以对两种走法分别计算最远走多少步再加起来就是答案,以 \(+A,-B\) 为例。
不妨设进行了 \(k\) 次 \(+A\),因为走不动了,所以最终的数 \(x\) 一定满足 \(x\in [m-A+1,B-1]\),那么一定进行了 $\lfloor \frac{V+kA}{B} \rfloor $ 次 \(-B\) 。
也就是说,我们要找最小的 \(k\),满足 $(V+kA)\bmod B+A\geq M+1 $ 。
令 \(V0=V\bmod B\),若 \(V0+A>M\),显然此时答案为 $\lfloor \frac{V}{B} \rfloor $ 。
否则:
引理四:\(V0+(kA\bmod B)<B\)
若不然,我们先减去 \(B\),然后 \(+A\),一定合法,矛盾。
因此 \((V+kA)\bmod B=V0+(kA\bmod B)\),转为计算 \(L\leq (kA)\bmod B\leq R\) 的最小的 \(k\) 。
考虑使用类欧求解。
令 \(B=pA+q,t=\lfloor \frac{kA}{B} \rfloor\),那么 \((kA)\bmod B=kA-Bt=kA-(pA+q)t=A(k-tp)-tq\),可以变成 \(tq\bmod A\in [L',R']\),递归计算。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int Euclid(int A,int B,int L,int R)
{
if(A==0) return 0;
A%=B;
if((L-1)/A!=R/A) return (L+A-1)/A;
int t=Euclid(B%A,A,(A-R%A)%A,(A-L%A)%A);
return (1ll*t*B+L+A-1)/A;
}
int walk(int A,int B,int V,int M)
{
int V0=V%M;
if(V0+A>M) return V/B;
int k=Euclid(A,B,M-A-V0+1,B-V0-1);
return k+(V+1ll*A*k)/B;
}
int main()
{
int T;
cin>>T;
while(T--)
{
int A,B,V,M;
scanf("%d %d %d %d",&A,&B,&V,&M);
if(A+B-1<=M)printf("%d\n",M+1);
else printf("%d\n",walk(A,B,V%A,M)+walk(B,A,V%A,M)+1);
}
return 0;
}
[ARC132E] Paw
考虑最终形态,一定是一段前缀是 \(<\),一段后缀是 \(>\),中间保持不变且没有洞。
如果有 \(k\) 个洞,那么一共有 \(k+1\) 种合法的形态,贡献是好算的,重点是算这种情况发生的概率。
容易发现前缀和后缀是相同的,设 \(f_i\) 表示把 \(i\) 个洞填满,不覆盖到 \(i\) 之后的概率。
如果把覆盖洞的顺序携程一个序列,那么要求就是,所有的后缀最大值都必须朝左。
考虑 \(1\) 号洞,除非他在最后,否则不可能是后缀最大值,因此 \(f_i=f_{i-1}\frac{2i-1}{2i}\) 。
复杂度 \(O(n)\) 。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int n,m;
char s[N];
int f[N],a[N],p[N];
const int mod = 998244353;
int inv[N];
const int inv2=(mod+1)/2;
int main()
{
cin>>n;
scanf("%s",s+1);
p[++m]=0;
for(int i=1;i<=n;i++)
{
a[i]=a[i-1];
if(s[i]=='<')a[i]++;
if(s[i]=='.')p[++m]=i;
}
p[++m]=n+1;
inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
f[0]=1;
for(int i=1;i<=n;i++)
f[i]=1ll*f[i-1]*(2*i-1)%mod*inv2%mod*inv[i]%mod;
int ans=0;
for(int i=1;i<m;i++)
{
int coef=1ll*f[i-1]*f[m-i-1]%mod;
ans=(ans+1ll*(p[i]+a[p[i+1]-1]-a[p[i]])*coef%mod)%mod;
}
cout<<ans;
return 0;
}
[ARC080F] Prime Flip
首先做异或差分,那么每次可以选择 \(i,j\),满足 \(j-i\in prime\),反转 \(b_i,b_j\),最终要把所有数字都变成 0。
考虑对于 \(j-i\) 分类讨论:
- \(j-i\) 是奇质数
最优方案就是 \(1\) . - \(j-i\) 是偶数
若 \(j-i=2\),我们可以分别操作长度为 \(5,3\) 的区间。
若 \(j-i=4\),我们可以分别操作长度为 \(7,3\) 的区间。
否则根据哥德巴赫猜想一定可以把区间分成两个奇质数之和,因此操作次数总是为 \(2\) 。 - \(j-i\) 是非质数的奇数
同上可证明一定可以操作次数为 \(3\) 。
有一个贪心的做法是:先操作第一类,再操作第二类,再操作第三类,可以证明这样总是优的。
注意到满足第一类的两个数奇偶性一定不同,用二分图匹配求出最大匹配,并且第二类两个数是相等的,因此一定是在剩下的数里尽可能每一类两两匹配,最后要么什么也没有,要么剩下一对,用第三类就行了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 220;
struct edge
{
int y,next;
}e[N*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
int match[N];
bool vis[N];
int n;
bool isprime(int x)
{
if(x<3)return 0;
for(int i=2;i*i<=x;i++)
if(x%i==0) return 0;
return 1;
}
int dfs(int x)
{
if(vis[x])return 0;
vis[x]=1;
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
if(!match[y]||dfs(match[y]))
{
match[y]=x;
return 1;
}
}
return 0;
}
int p[N],a[N];
int main()
{
int m=0;
cin>>m;
a[0]=-1e9;
for(int i=1;i<=m;i++)
{
scanf("%d",&a[i]);
if(a[i]>a[i-1]+1)
{
if(i>1)p[++n]=a[i-1]+1;
p[++n]=a[i];
}
}
p[++n]=a[m]+1;
for(int i=1;i<=n;i++)if(p[i]%2==0)
for(int j=1;j<=n;j++)if(p[j]%2==1)
if(isprime(abs(p[i]-p[j])))add(i,j);
int ans=0;
int A=0,B=0;
for(int i=1;i<=n;i++)
if(p[i]%2==0)
{
memset(vis,0,sizeof(vis));
ans+=dfs(i);
A++;
}
else B++;
A-=ans;B-=ans;
ans+=(A/2)*2;A%=2;
ans+=(B/2)*2;B%=2;
if(A&&B)ans+=3;
cout<<ans;
return 0;
}
[ARC122F] Domination
巨大神仙题。
首先,如果一个红点右上方还有其他点,这个点可以被删掉,那么剩下的点满足横坐标递增,纵坐标递减。
也就是说,对于一个蓝点,它左下方的红点构成了一个区间。
首先考虑 \(k=1\) 的情况。
注意到一个点覆盖两个区间 \([l_1,r_1],[l_2,r_2]\) 的代价和不小于覆盖 \([l_1,r_2]\) 的,因此不妨认为一个点可以被多次使用,这不影响答案。
考虑一个 \(dp\),设 \(f_i\) 为覆盖前 \(i\) 个红色点且最后一个区间右端点为 \(i\) 的最小花费,转移时枚举左端点 \(j\),以及选取的石头 \(k\),转移为 \(f_i=f_{j-1}+ \max(x_i-x_k,0)+\max(y_j-y_k,0)\) 。
考虑把每个石头拆成两个点,分别代表其 \(x\) 坐标和 \(y\) 坐标,然后分别从小到大排序。
对于 \(x\) 坐标拆成的点,从排序后第 \(i\) 个连向 \(i+1\),代价为 \(x_{i+1}-x_i\),再连反向边,代价为 \(0\) .
对于 \(y\) 坐标拆成的点,从排序后第 \(i\) 个连向 \(i-1\),代价为 \(y_i-y_{i-1}\),再连反向边,代价为 \(0\) .
对于每个蓝点,从对应 \(y\) 点向 \(x\) 点连 0 边。
那么从 \(y_j\) 到 \(x_i\) 的最短路就是,\(\max(x_i-x_k,0)+\max(y_j-y_k,0)\) 。
在此基础上连边 \(x_i,y_{i+1}\),那么从 \(y_1\) 到 \(x_n\) 的最短路就是答案。
对于 \(k\) 任意的情况,等价于找出 \(k\) 条路径覆盖就行了,在上图基础上限制蓝点的边流量为 \(1\),其余边流量为 \(inf\),求流量为 \(k\) 的最小费用最大流就可以了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+7;
typedef long long LL;
const int INF = 1e9;
int tot=0,S,T;
namespace flow
{
struct edge
{
int y,next,w;
}e[6*N];
int link[N],t=1;
int f[6*N];
void add(int x,int y,int v,int w)
{
e[++t].y=y;
e[t].w=w;
f[t]=v;
e[t].next=link[x];
link[x]=t;
}
void Link(int x,int y,int v,int w)
{
add(x,y,v,w);
add(y,x,0,-w);
}
#define PII pair<LL,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
priority_queue<PII> q;
LL d[N],h[N];
bool vis[N];
int pre[N];
void dijkstra()
{
for(int i=1;i<=tot;i++)
{
d[i]=1e18;
vis[i]=0;
}
d[S]=0;
q.push(mk(-d[S],S));
while(!q.empty())
{
int x=q.top().second;
q.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
LL v=e[i].w+h[x]-h[y];
if(f[i]&&d[y]>d[x]+v)
{
d[y]=d[x]+v;
pre[y]=i;
q.push(mk(-d[y],y));
}
}
}
}
LL dinic(int K)
{
LL ans=0;
while(K--)
{
dijkstra();
ans+=d[T]-(h[S]-h[T]);
for(int i=T;i!=S;i=e[pre[i]^1].y)
{
f[pre[i]]--;
f[pre[i]^1]++;
}
for(int i=1;i<=tot;i++)
h[i]+=d[i];
}
return ans;
}
}
struct node
{
int x,y;
}A[N],B[N],C[N];
int n,m,K;
bool cmp(node X,node Y)
{
if(X.x==Y.x) return X.y>Y.y;
return X.x>Y.x;
}
struct Pot
{
int x,o;
}Px[N*2],Py[N*2];
int Cx=0,Cy=0;
int Aid[N][2],Bid[N][2];
bool cmp2(Pot X,Pot Y)
{
return X.x<Y.x;
}
int main()
{
cin>>n>>m>>K;
for(int i=1;i<=n;i++)scanf("%d %d",&C[i].x,&C[i].y);
for(int i=1;i<=m;i++)scanf("%d %d",&B[i].x,&B[i].y);
sort(C+1,C+n+1,cmp);
LL mx=-1;
int nn=0;
for(int i=1;i<=n;i++)
{
if(C[i].y>mx)
{
mx=C[i].y;
A[++nn]=C[i];
}
}
n=nn;
reverse(A+1,A+n+1);
for(int i=1;i<=n;i++)
{
Aid[i][0]=++tot;
Aid[i][1]=++tot;
Px[++Cx]=(Pot){A[i].x,Aid[i][0]};
Py[++Cy]=(Pot){A[i].y,Aid[i][1]};
}
for(int i=1;i<=m;i++)
{
Bid[i][0]=++tot;
Bid[i][1]=++tot;
Px[++Cx]=(Pot){B[i].x,Bid[i][0]};
Py[++Cy]=(Pot){B[i].y,Bid[i][1]};
}
sort(Px+1,Px+Cx+1,cmp2);
sort(Py+1,Py+Cy+1,cmp2);
S=++tot;T=++tot;
for(int i=1;i<Cx;i++)
{
flow::Link(Px[i].o,Px[i+1].o,INF,Px[i+1].x-Px[i].x);
flow::Link(Px[i+1].o,Px[i].o,INF,0);
}
for(int i=1;i<Cy;i++)
{
flow::Link(Py[i].o,Py[i+1].o,INF,0);
flow::Link(Py[i+1].o,Py[i].o,INF,Py[i+1].x-Py[i].x);
}
for(int i=1;i<n;i++)
flow::Link(Aid[i][0],Aid[i+1][1],INF,0);
for(int i=1;i<=m;i++)
flow::Link(Bid[i][1],Bid[i][0],1,0);
flow::Link(S,Aid[1][1],K,0);
flow::Link(Aid[n][0],T,K,0);
cout<<flow::dinic(K);
return 0;
}
</details>

浙公网安备 33010602011771号