2024 Noip 做题记录(三)
Round #9 - 2024.9.23
A. [P10849] Level
题目大意
给定若干人和空位,等级
,其中等级为 的人和空位分别有 个,给每个人匹配一个位置,如果一个等级为 的人匹配了一个等级为 的位置,会产生 的收益,最大化收益。 数据范围:
。
思路分析
考虑贪心,显然优先最大化
然后尽可能多地保留
时间复杂度:
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
int n,a[MAXN],b[MAXN],s[MAXN];
signed main() {
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]);
int w=0;
for(int i=1,j=0;i<=n;++i) {
while(b[i]&&j) {
int x=min(b[i],a[s[j]]);
w+=x,b[i]-=x,a[s[j]]-=x;
if(!a[s[j]]) --j;
}
s[++j]=i;
}
for(int i=1;i<=n;++i) w-=max(0,b[i]-a[i]);
printf("%d\n",w);
return 0;
}
B. [P10538] Route
题目大意
个点的城市, 条线路有起点、终点、开始时间、结束时间、费用,还有 个限制表示 时间段内,可以在某条线路上免费吃饭,或者在某个城市 以 的代价吃饭,求 的最短路(所有限制都要满足)。 数据范围:
。
思路分析
直接建图最短路较为困难,考虑 dp,用
按每条路径的开始时间或结束时间排序,那么转移一定是从前到后的,对于每条边
我们就要在所有以
暴力转移复杂度是平方的,考虑优化。
我们发现转移的代价函数是区间内限制数个数,这显然满足四边形不等式,故有决策单调性。
考虑二分优化,每次求出一条新的边
查询一条边的
二分的时候要做一个静态二维数点,主席树维护即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef vector<int> vi;
const int MAXN=1e5+5,V=1e9+5;
const ll inf=1e18;
struct SegmentTree {
int tot,sum[MAXN*32],ls[MAXN*32],rs[MAXN*32];
void ins(int u,int l,int r,int q,int &p) {
sum[p=++tot]=sum[q]+1;
if(l==r) return ;
int mid=(l+r)>>1;
if(u<=mid) ins(u,l,mid,ls[q],ls[p]),rs[p]=rs[q];
else ins(u,mid+1,r,rs[q],rs[p]),ls[p]=ls[q];
}
int qry(int u,int l,int r,int p) {
if(!p||u<=l) return sum[p];
int mid=(l+r)>>1,s=0;
if(u<=mid) s=qry(u,l,mid,ls[p]);
return s+qry(u,mid+1,r,rs[p]);
}
} TR;
int n,m,q,rt[MAXN];
struct Seg {
int l,r;
bool operator <(const Seg &oth) const { return r<oth.r; }
} o[MAXN];
struct Edge {
int u,v,a,b,w;
} e[MAXN];
int qry(int l,int r) {
if(l>r) return 0;
int i=upper_bound(o+1,o+q+1,Seg{l,r})-o-1;
return TR.qry(l,1,V,rt[i]);
}
struct ds {
int w;
struct info {
int l,r,x; ll d;
};
vector <info> q;
ll f(const info&p,int x) {
if(p.x>x) return inf;
return p.d+1ll*w*qry(p.x+1,x-1);
}
#define tl (q.back())
void ins(int x,ll d) {
info z={0,0,x,d};
while(q.size()&&f(tl,tl.l)>=f(z,tl.l)) q.pop_back();
if(q.empty()) return q.push_back({x,V,x,d});
int l=tl.l,r=tl.r,s=r+1;
while(l<=r) {
int mid=(l+r)>>1;
if(f(tl,mid)>=f(z,mid)) s=mid,r=mid-1;
else l=mid+1;
}
if(s<=V) q.back().r=s-1,q.push_back({s,V,x,d});
}
#undef tl
ll calc(int x) {
if(q.empty()) return -1;
int l=0,r=q.size()-1,i=r;
while(l<=r) {
int mid=(l+r)>>1;
if(x<=q[mid].r) i=mid,r=mid-1;
else l=mid+1;
}
if(x<q[i].x) return -1;
return f(q[i],x);
}
} dp[MAXN];
ll Main() {
sort(o+1,o+q+1);
for(int i=1;i<=q;++i) TR.ins(o[i].l,1,V,rt[i-1],rt[i]);
sort(e+1,e+m+1,[&](auto s,auto t){ return s.b<t.b; });
dp[1].ins(0,0);
for(int i=1;i<=m;++i) {
ll f=dp[e[i].u].calc(e[i].a);
if(~f) dp[e[i].v].ins(e[i].b,f+e[i].w);
}
return dp[n].calc(V);
}
ll solve(int N,int M,int W,vi T,vi X,vi Y,vi A,vi B,vi C,vi L,vi R) {
n=N,m=M,q=W;
for(int i=1;i<=n;++i) dp[i].w=T[i-1];
for(int i=0;i<m;++i) e[i+1]={X[i]+1,Y[i]+1,A[i],B[i],C[i]};
for(int i=0;i<q;++i) o[i+1]={L[i],R[i]};
return Main();
}
C. [P10833] Permutation
题目大意
给定长度为
的序列 ,求有多少区间 使得 是 的排列。 数据范围:
。
思路分析
考虑 CDQ 分治,我们用如下两个条件刻画一个好的区间:
- 区间最大值等于区间长度。
- 区间没有重复元素。
设当前分治区间中
那么第一个条件等于
我们只要判断
如果两个重复的元素都在
然后考虑
从小到大枚举
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int n,ans=0,a[MAXN],mx[MAXN],lp[MAXN],rp[MAXN];
void cdq(int l,int r) {
if(l==r) return ans+=(a[l]==1),void();
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
mx[mid]=a[mid],mx[mid+1]=a[mid+1];
for(int i=mid-1;i>=l;--i) mx[i]=max(mx[i+1],a[i]);
for(int i=mid+2;i<=r;++i) mx[i]=max(mx[i-1],a[i]);
int L=l,R=r;
for(int i=mid;i>=l;--i) {
if(lp[a[i]]) { L=i+1; break; }
lp[a[i]]=i;
}
for(int i=mid+1;i<=r;++i) {
if(rp[a[i]]) { R=i-1; break; }
rp[a[i]]=i;
}
for(int i=mid+1,j=mid+1,p=L;i<=R;++i) {
while(j>L&&mx[j-1]<=mx[i]) --j;
if(lp[a[i]]) p=max(p,lp[a[i]]+1);
if(1<=mx[i]-(i-mid)&&mx[i]-(i-mid)<=mid-max(j,p)+1) ++ans;
}
for(int i=mid,j=mid,p=R;i>=L;--i) {
while(j<R&&mx[j+1]<mx[i]) ++j;
if(rp[a[i]]) p=min(p,rp[a[i]]-1);
if(1<=mx[i]-(mid+1-i)&&mx[i]-(mid+1-i)<=min(j,p)-mid) ++ans;
}
for(int i=l;i<=mid;++i) lp[a[i]]=0;
for(int i=mid+1;i<=r;++i) rp[a[i]]=0;
}
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
cdq(1,n),printf("%d\n",ans);
return 0;
}
*D. [P10004] Inverse
题目大意
给定
,对于所有 计数有多少 阶排列 满足 。 数据范围:
。
思路分析
先考虑只有
回到这题,考虑二元二项式反演,钦定
然后只需要考虑
观察
那么对于
那么我们就是要计数有多少
这里再二维容斥一遍,即钦定
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,m;
inline void init(ull B) { b=B,m=ull((uLL(1)<<64)/B); }
inline ull mod(ull a) {
ull q=((uLL(m)*a)>>64),r=a-q*b;
return r>=b?r-b:r;
}
};
#define o(x) FastMod::mod(x)
const int MAXN=505,MAXV=2.6e5+5;
int n,MOD;
ll C[MAXN][MAXN],fac[MAXV],ifac[MAXV];
ll g[MAXN][MAXN],f[MAXN][MAXN],h[MAXN];
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
signed main() {
scanf("%d%d",&n,&MOD),FastMod::init(MOD);
for(int i=fac[0]=1;i<MAXV;++i) fac[i]=fac[i-1]*i%MOD;
ifac[MAXV-1]=ksm(fac[MAXV-1]);
for(int i=MAXV-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) g[i][j]=fac[i*j+n-1]%MOD*ifac[i*j-1]%MOD*ifac[n]%MOD;
for(int i=1;i<=n;++i) {
memset(h,0,sizeof(h));
for(int j=1;j<=n;++j) {
for(int k=1;k<=i;++k) {
h[j]=o(h[j]+((i-k)&1?MOD-C[i][k]:C[i][k])*g[k][j]);
}
}
for(int j=1;j<=n;++j) {
for(int k=0;k<=j;++k) {
f[i][j]=o(f[i][j]+((j-k)&1?MOD-C[j][k]:C[j][k])*h[k]);
}
}
}
memset(g,0,sizeof(g));
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) g[n-i][n-j]=f[i][j];
memset(f,0,sizeof(f));
for(int i=0;i<n;++i) {
memset(h,0,sizeof(h));
for(int j=0;j<n;++j) {
for(int k=i;k<n;++k) {
h[j]=o(h[j]+((k-i)&1?MOD-C[k][i]:C[k][i])*g[k][j]);
}
}
for(int j=0;j<n;++j) {
for(int k=j;k<n;++k) {
f[i][j]=o(f[i][j]+((k-j)&1?MOD-C[k][j]:C[k][j])*h[k]);
}
}
}
for(int i=0;i<n;++i) {
for(int j=0;j<n;++j) printf("%lld ",f[i][j]);
puts("");
}
return 0;
}
*E. [P10005] Prefix
题目大意
定义一个长度为
的序列 的权值为 ,其中 是 的前缀和。 给定
,求所有长度为 值域 且互不相同的序列的权值和。 数据范围:
。
思路分析
从
可以设计出如下的问题:
对于
考虑每种颜色依次插入,得到的总概率就是
假设我们确定了所有
因此确定所有
因此我们 dp 求出
然后考虑
此时相当于给答案
假如我们钦定一组
此时
求对应的方案数,先排列
化简得到原式等于:
注意到我们求的是概率,因此前面那个表示方案数的式子需要除掉。
因此一组
考虑直接 dp 维护,设
转移时枚举
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=105,MAXS=1e4+5;
int n,m,k,MOD;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll inv[MAXS];
namespace k1 {
ll f[MAXN];
void main() {
f[0]=1;
for(int i=1;i<=m;++i) for(int j=n;j;--j) f[j]=(f[j]+f[j-1]*inv[i])%MOD;
printf("%lld\n",f[n]);
}
}
namespace k2 {
int up[MAXN];
ll f[MAXN][MAXS][2],g[MAXN][MAXS][2];
void add(ll &x,ll y) { x=(x+y)%MOD; }
void main() {
for(int i=1;i<=m;++i) up[i]=(i<=n)?i*(i+1)/2:up[i-1]+n;
f[0][0][0]=1;
for(int i=1;i<=m;++i) {
memcpy(g,f,sizeof(g));
for(int j=0;j<n;++j) for(int s=0;s<=up[i-1];++s) for(int o:{0,1}) {
ll &w=f[j][s][o]; if(!w) continue;
add(g[j+1][s][o],inv[i]*w); //choose in A
add(g[j+1][s+i][o],(MOD-inv[i])*w); //choose in B
if(!o) add(g[j][s+i][1],i*w); //choose as a[1]
}
memcpy(f,g,sizeof(f));
}
ll ans=0;
for(int s=0;s<=up[m];++s) ans=(ans+f[n-1][s][1]*inv[s])%MOD;
printf("%lld\n",ans);
}
}
signed main() {
scanf("%d%d%d%d",&n,&m,&k,&MOD);
for(int i=1;i<MAXS;++i) inv[i]=ksm(i);
k==1?(k1::main()):(k2::main());
return 0;
}
Round #10 - 2024.9.24
A. [P10524] Shift
题目大意
给定长度为
的序列 ,求一个循环移位最大化 。 数据范围:
。
思路分析
考虑暴力枚举循环移位的大小,即每次将所有元素左移一位,动态维护三个答案。
用支持全局
我们在深度为
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1<<21;
int n,a[MAXN],d[MAXN],tg[MAXN],f[MAXN];
ll X[MAXN],Y[MAXN],Z[MAXN];
void ins(int p,int u,int x) {
if(p>1) f[p]+=x>>(d[p]-1)&1;
if(d[p]==n) return ;
int c=u>>d[p]&1;
ins(p<<1|c,u,x);
}
void psu(int p) {
int k=d[p],s=1<<(n-k-1),ls=p<<1|tg[p],rs=ls^1;
X[p]=X[ls]+X[rs]+((ll)(f[ls]+s-f[rs])<<k);
Y[p]=Y[ls]+Y[rs]+((ll)f[rs]<<k);
Z[p]=Z[ls]+Z[rs]+((ll)(f[ls]+s)<<k);
}
void rot(int p) {
if(d[p]==n) return ;
rot(p<<1|tg[p]),tg[p]^=1,psu(p);
}
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=2;i<(2<<n);++i) d[i]=d[i>>1]+1;
for(int i=0;i<(1<<n);++i) cin>>a[i],ins(1,i,a[i]);
for(int i=(1<<n)-1;i;--i) psu(i);
ll sx=X[1],sy=Y[1],sz=Z[1];
for(int i=1;i<(1<<n);++i) rot(1),sx=max(sx,X[1]),sy=max(sy,Y[1]),sz=max(sz,Z[1]);
cout<<sx<<" "<<sy<<" "<<sz<<"\n";
return 0;
}
B. [P10332] Order
题目大意
给定一棵
个点的树,每次可以将和已选集合相邻的一个点 加入集合(初始必须先加入根),有 的概率直接结束,否则会获得 的收益并继续,最大化收益期望。 数据范围:
。
思路分析
给每个
如果无拓扑序限制,这是经典的 Exchange Argument 问题,按
在树上就是树上 Exchange Argument,每次找到
由于使用懒惰删除,因此要维护每个点当前的合并次数以判断是否是最新版本。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=1e5+5;
struct info {
ld p,w; int id,s;
friend bool operator <(const info &u,const info &v) {
return u.w+u.p*v.w<v.w+v.p*u.w;
}
} a[MAXN];
int n,fa[MAXN],dsu[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
signed main() {
scanf("%d",&n);
priority_queue <info> Q;
for(int i=1;i<=n;++i) scanf("%Lf",&a[i].w),a[i].id=i,a[i].s=1;
for(int i=1;i<=n;++i) scanf("%Lf",&a[i].p),a[i].w*=a[i].p;
for(int i=2;i<=n;++i) scanf("%d",&fa[i]),Q.push(a[i]);
iota(dsu+1,dsu+n+1,1);
while(Q.size()) {
auto z=Q.top(); int u=z.id; Q.pop();
if(z.s!=a[u].s||dsu[u]!=u) continue;
int x=find(fa[u]);
a[x].w+=a[x].p*a[u].w,dsu[u]=x;
a[x].p*=a[u].p,a[x].s+=a[u].s;
if(x>1) Q.push(a[x]);
}
printf("%.18Lf\n",a[1].w);
return 0;
}
*C. [P10042] Iterate
题目大意
给定
网格,每个格子填 ,有些还没填,你要给他们填入 。 一个网格会进行若干轮迭代,对于一个填
的格子,如果他四联通的格子中有 ,那么迭代后这个格子也会变成 ,在 意义下考虑。 一个网格是好的当且仅当其不会无限迭代,定义其权值为
最后一次迭代时共迭代了几次。 数据范围:
。
思路分析
考虑把
如果确定
在
那么一个
因此答案就是所有最小值中到
因此只要
对于每个
因此第一问可以简单 dp,
然后考虑第二问,设
如果暴力记录状态还要记录
然后要维护到
考虑类似刚才的技巧,设最小距离为
而
因此设计状态
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=60,MAXS=255,MOD=998244353;
const int pw[6]={1,3,9,27,81,243},d[3][3]={{0,1,-1},{-1,0,1},{1,-1,0}};
int n,m,Q,a[MAXN][5];
bool ok[55][MAXS],trs[MAXS][MAXS];
int v[MAXS],p[MAXS]; //min prefix, pos
int f1[MAXS][MAXN][5],f2[MAXS][MAXN][5];
//(last color,cur min-a[i,0],mindis-i) {wys ans}
int g1[MAXS][MAXN][5],g2[MAXS][MAXN][5];
int dg(int s,int i) { return s/pw[i]%3; }
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void add(int &x,long long y) { x=(x+y)%MOD; }
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m,Q=pw[n];
for(int i=0;i<n;++i) for(int j=0;j<m;++j) cin>>a[j][i];
for(int i=0;i<m;++i) for(int s=0;s<Q;++s) {
ok[i][s]=true;
for(int j=0;j<n;++j) ok[i][s]&=(a[i][j]==3||a[i][j]==dg(s,j));
}
for(int s=0;s<Q;++s) {
v[s]=p[s]=0;
for(int i=1,k=0;i<n;++i) {
k+=d[dg(s,i-1)][dg(s,i)];
if(k<v[s]) v[s]=k,p[s]=i;
}
for(int t=0;t<Q;++t) {
trs[s][t]=true;
for(int i=1;i<n;++i) {
int x=dg(s,i-1),y=dg(t,i-1),z=dg(t,i),w=dg(s,i);
trs[s][t]&=!(d[x][y]+d[y][z]+d[z][w]+d[w][x]);
}
}
}
for(int s=0;s<Q;++s) if(ok[0][s]) f1[s][-v[s]][p[s]]=1,f2[s][-v[s]][p[s]]=p[s];
for(int i=1;i<m;++i) {
memset(g1,0,sizeof(g1)),memset(g2,0,sizeof(g2));
for(int s=0;s<Q;++s) for(int j=0;j<=i+n;++j) for(int k=0;k<n;++k) for(int t=0;t<Q;++t) {
if(!ok[i][t]||!trs[s][t]) continue;
int w=(d[t%3][s%3]-j)-v[t],z=f1[s][j][k];
if(!z) continue;
if(w<0||(w==0&&k-1<=p[t])) { //remain min
add(g1[t][j-d[t%3][s%3]][max(k-1,0)],z);
add(g2[t][j-d[t%3][s%3]][max(k-1,0)],f2[s][j][k]);
} else { //min is cur
add(g1[t][-v[t]][p[t]],z);
add(g2[t][-v[t]][p[t]],1ll*z*(i+p[t]));
}
}
memcpy(f1,g1,sizeof(f1)),memcpy(f2,g2,sizeof(f2));
}
int s1=0,s2=0;
for(int s=0;s<Q;++s) for(int j=0;j<=m+n;++j) for(int k=0;k<n;++k) {
add(s1,f1[s][j][k]),add(s2,f2[s][j][k]);
}
printf("%d %d\n",s1,s2);
return 0;
}
D. [P10010] Optimize
题目大意
给定
,选出一个子集 最大化 。 数据范围:
,数据随机。
思路分析
由于数据随机,因此考虑一些随机化算法,随机一组初始态出发开始爬山,每次翻转一个点的状态,如果更优就转移。
但这样显然难以通过,利用数据随机的性质,我们发现此时
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const int MAXN=3005;
mt19937 rnd(998244353);
int n,id[MAXN];
ll a[MAXN],b[MAXN];
bool s[MAXN];
void solve() {
for(int i=1;i<=n;++i) cin>>a[i]>>b[i],id[i]=i;
LL Z=0;
for(int o=32;o;--o) {
shuffle(id+1,id+n+1,rnd);
LL A=0,B=0;
for(int i=1;i<=n/2;++i) s[i]=0,A+=a[i];
for(int i=n;i>n/2;--i) s[i]=1,B+=b[i];
LL X=A*B;
for(int q=62500;q;--q) {
int x=rnd()%n+1;
if(s[x]) {
if(X<=(A+a[x])*(B-b[x])) s[x]^=1,A+=a[x],B-=b[x],X=A*B;
} else {
if(X<=(A-a[x])*(B+b[x])) s[x]^=1,A-=a[x],B+=b[x],X=A*B;
}
}
Z=max(Z,X);
}
string z;
while(Z) z+=Z%10+'0',Z/=10;
reverse(z.begin(),z.end());
cout<<z<<"\n";
}
signed main() {
ios::sync_with_stdio(false);
int T; ll A,B; cin>>T>>n>>A>>B;
while(T--) solve();
return 0;
}
*E. [P10001] Coupon
题目大意
给定
个物品,你要按顺序购买每个物品,初始有 个优惠券。 对于第
个物品,你要花费总计 个金币或优惠券,其中优惠券至多用 张,并且你每付出 个金币就会获得一张优惠券(向下取整)。 最小化花费的金币总数。
数据范围:
。
思路分析
假设第
考虑如何贪心解决这个问题。
我们分析优惠券从
- 使用的前
张优惠券:使用任意多张都不会减少获得的优惠券,因此贪心使用尽可能多的这种优惠券。 - 接下来每使用
张优惠券,都会使得后续剩余优惠券数量额外 。 - 对于剩余的最后
张优惠券,还会使得后续剩余优惠券数量 。
容易发现所有的操作中,第一种操作显然最优,然后是第二种和第三种。
那么我们先进行所有的第一种操作,容易发现在哪个位置操作仅仅相当于改变
然后我们要进行一些第二种操作,即在某个位置连续使用
我们发现这种情况下收益相同,使用的位置显然越靠后后效性越小,因此从后往前贪心取尽可能多的
那么此时第
最后第三种情况,我们只要考虑每个点剩余使用的优惠券数
根据贪心,我们依然要优先做收益最高的操作,即可以使用优惠券数最多的操作。
记
观察这个结构的性质,我们发现
那么考虑模拟这个过程,我们从大往小枚举
考虑如何维护这些位置,首先这些位置显然满足
而
容易发现我们只要处理等于某个
我们可以用线段树动态维护
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
int n,id[MAXN];
ll c,a[MAXN],b[MAXN],s[MAXN],x[MAXN],up[MAXN];
struct SegmentTree {
ll tr[MAXN<<2],tg[MAXN<<2];
void psu(int p) { tr[p]=min(tr[p<<1],tr[p<<1|1]); }
void adt(int p,int k) { tr[p]+=k,tg[p]+=k; }
void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void init(int l=1,int r=n+1,int p=1) {
tr[p]=tg[p]=0;
if(l==r) return tr[p]=s[l-1]-x[l],void();
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
psu(p);
}
void add(int ul,int ur,int k,int l=1,int r=n+1,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
ll qry(int ul,int ur,int l=1,int r=n+1,int p=1) {
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1; psd(p);
if(ur<=mid) return qry(ul,ur,l,mid,p<<1);
if(mid<ul) return qry(ul,ur,mid+1,r,p<<1|1);
return min(qry(ul,ur,l,mid,p<<1),qry(ul,ur,mid+1,r,p<<1|1));
}
} T;
void solve() {
scanf("%d%lld%lld",&n,&s[0],&c);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
for(int i=1;i<=n;++i) scanf("%lld",&b[i]);
for(int i=1;i<=n;++i) {
ll w=min({a[i]%c,b[i],s[i-1]});
x[i]=w,s[i]=s[i-1]-x[i]+(a[i]-x[i])/c;
}
ll lim=s[n];
for(int i=n;i>=1;--i) {
ll w=min({(b[i]-x[i])/c,(s[i-1]-x[i])/c,lim/(c+1)});
x[i]+=c*w,lim=min(lim-(c+1)*w,s[i-1]-x[i]);
}
for(int i=1;i<=n;++i) s[i]=s[i-1]-x[i]+(a[i]-x[i])/c;
x[n+1]=id[n+1]=0,T.init();
for(int i=1;i<=n;++i) id[i]=i,up[i]=min(c-1,b[i]-x[i]);
sort(id+1,id+n+1,[&](int i,int j){ return up[i]>up[j]; });
priority_queue <int> Q;
for(int i=1,j;i<=n;i=j) {
for(j=i;j<=n&&up[id[j]]==up[id[i]];++j) Q.push(id[j]);
while(Q.size()) {
int u=Q.top();
ll z=min({up[u],T.qry(u,u),T.qry(u+1,n+1)-1});
if(z>up[id[j]]) {
Q.pop(),x[u]+=z,T.add(u,u,-z),T.add(u+1,n+1,-z-1);
} else break;
}
}
ll ans=0;
for(int i=1;i<=n;++i) ans+=a[i]-x[i];
printf("%lld\n",ans);
}
signed main() {
int o; scanf("%d",&o);
while(o--) solve();
return 0;
}
*F. [P10062] Latin
题目大意
给定
矩阵的前 行 列元素,构造一个完整矩阵使得每一行每一列都是 排列。 数据范围:
。
思路分析
先考虑
此时一定有解,可以依次构造每一行,建立二分图,左部点是这一行的所有位置,右部点是
此时所有点的度数都是
考虑如何构造,如果不对每一行分别求匹配,那么我们就是要把这张正则二分图分解成
事实上,这就是二分图最小边染色,下面给出做法:
对于任意一张二分图,其最小边染色定义为用最少的颜色给每条边染色,且每个点的所有出边两两异色。
事实上最小边染色的大小就是所有顶点的最大度数,这显然是一个下界,现给出构造性证明。
考虑每次加入一条边
,如果 的出边中的未选用元素集有公共元素,直接设为这种颜色。 否则任选一种
的出边中未选用的颜色 和 的出边种未选用的颜色 。 找到
的 颜色出边对应的端点 ,然后将 设成颜色 ,然后 设成颜色 。 此时
的出边中可能出现两个 ,把另外一个设成颜色 ,可以证明在二分图上这样的调整过程总会停止。 关于此构造的正确性与时间复杂度的证明:
该构造过程只更改了原颜色为
或 的颜色的边,所以暂时不考虑图上其它的边。 此时,每个点的度数不超过
,所以此时图由若干链与环构成,且所有的链或环上边的颜色是 , 交替出现的。 又因为
一开始没有颜色 的邻边, 一开始没有颜色 的邻边,所以 均是孤立点或链的一端。 假设
是同一条链的两端,则该链 端的边的颜色为 , 端的颜色为 ,由于链上颜色交替出现,所以该链的长度(边数)是偶数;同时,由于 在二分图的两侧,所以该链的长度是奇数。推出矛盾,所以 是两条不同的链的端点。 容易发现,这个构造只会扫描
所在的链,因此这个构造一定会结束,而且只会扫到 条边。
然后考虑
如果右部点中存在度数
否则类似地求出一组二分图边染色即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=505;
struct bip {
int X,Y,g[MAXN<<1][MAXN];
void init(int x,int y) {
X=x,Y=y;
for(int i=1;i<=X+Y;++i) memset(g[i],0,sizeof(g[i]));
}
void add(int u,int v) {
v+=X;
int x=1,y=1;
while(g[u][x]) ++x;
while(g[v][y]) ++y;
g[u][x]=v,g[v][y]=u;
if(x==y) return ;
for(int w=v,z=y;w;w=g[w][z],z^=x^y) swap(g[w][x],g[w][y]);
}
} G;
int d[MAXN],a[MAXN][MAXN];
bool vis[MAXN];
void solve() {
int n,R,C;
cin>>n>>R>>C;
for(int i=1;i<=n;++i) d[i]=R;
for(int i=1;i<=R;++i) for(int j=1;j<=C;++j) cin>>a[i][j],--d[a[i][j]];
for(int i=1;i<=n;++i) if(d[i]>n-C) return cout<<"No\n",void();
G.init(R,n);
for(int i=1;i<=R;++i) {
memset(vis,false,sizeof(vis));
for(int j=1;j<=C;++j) vis[a[i][j]]=true;
for(int j=1;j<=n;++j) if(!vis[j]) G.add(i,j);
}
for(int i=1;i<=R;++i) {
for(int j=1;j<=n-C;++j) a[i][j+C]=G.g[i][j]-R;
}
G.init(n,n);
for(int i=1;i<=n;++i) {
memset(vis,false,sizeof(vis));
for(int j=1;j<=R;++j) vis[a[j][i]]=true;
for(int j=1;j<=n;++j) if(!vis[j]) G.add(i,j);
}
for(int i=1;i<=n;++i) {
for(int j=1;j<=n-R;++j) a[j+R][i]=G.g[i][j]-n;
}
cout<<"Yes\n";
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cout<<a[i][j]<<" \n"[j==n];
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*G. [P10433] Stop
题目大意
给定
个点 条边的无向图,有一些节点是“停止节点”,现在有 枚棋子初始放在 。 游戏会进行若干轮,每轮依次考虑第
枚棋子,并将其在图上移动到某个“停止节点”停下(不能不动)。 对于每个
,求把第 枚棋子移动到 上,所有棋子移动距离和的最小值。 数据范围:
。
思路分析
我们发现第
假设我们钦定进行了
注意到这个棋子第一轮移动到某个“停止节点”后,接下来的每一轮都可以直接走到某个邻居再走回来,因此每轮至多走
设这个棋子第一轮至少移动
如果想要更优,那就必须要做到每轮移动只走一步,也就是走到两个相邻的“停止节点”上。
对于所有的走到相邻“停止节点”的路径,我们关心其长度
我们发现
证明:只需要证明
,即 。 考虑
对应路径,由于其终点是第一对相邻的“停止节点”,因此此前路径上的的每个“停止节点”的邻域都没有“停止节点”。 那么路径上这
个“停止节点”,在路径上经过的下一个点肯定不是停止节点,因此可以将这 个点取出。 并且考虑
表示这条路径上第一次到达“停止节点”前的长度,那么我们在这条长度为 的路径上删去 个点的贡献,并不影响 中点的贡献。 因此我们知道
,且 也对应一条走到“停止节点”的路径,故 ,因此 。
求出
容易发现其他时候肯定不优,此时我们表示出了
因此对于每个
那么一个朴素的想法就是对于每个
但这样做的复杂度难以接受,根据贪心的思想,我们自然想到求出经过轮数最小的一条路径,但这不对,因为我们可以略微增加经过的轮数,但使得路径长度减小。
但是这样路径长度减小量肯定是
那么我们发现轮数很大的情况并不优,因为每多走一轮,其他的
那么
因此我们可以分层图 bfs,时间复杂度
那么
我们枚举凸壳上的每条边,设其在
我们枚举
观察最短路径此时经过的轮数,如果这个轮数恰好落在
如果这个轮数落在
因此我们知道答案就是所有
由于凸包只有
对
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e4+5,inf=1e9,LIM=225;
int n,m,st[MAXN],cl[MAXN];
vector <int> G[MAXN];
int w1[MAXN],w2[MAXN];
ll ans[MAXN];
void bfs1() { //ans from x[1] without stop
memset(ans,0x3f,sizeof(ans));
queue <int> q; q.push(st[1]),ans[st[1]]=0;
while(q.size()) {
int u=q.front(); q.pop();
if(u!=st[1]&&cl[u]) continue;
for(int v:G[u]) if(ans[v]>n) ans[v]=ans[u]+1,q.push(v);
}
}
void bfs2() { //min dis to first stop
fill(w2+1,w2+n+1,inf);
queue <int> q;
for(int i=1;i<=n;++i) if(cl[i]) {
for(int j:G[i]) if(w2[j]==inf) w2[j]=1,q.push(j);
}
while(q.size()) {
int u=q.front(); q.pop();
for(int v:G[u]) if(w2[v]==inf) w2[v]=w2[u]+1,q.push(v);
}
}
void bfs3() { //min dis to 2 connected stops
deque <int> q;
fill(w1+1,w1+n+1,inf);
for(int i=1;i<=n;++i) if(cl[i]&&w2[i]==1) w1[i]=0,q.push_back(i);
static bool vis[MAXN];
fill(vis+1,vis+n+1,false);
while(q.size()) {
int u=q.front(); q.pop_front();
if(vis[u]) continue; vis[u]=true;
int d=w1[u]+1-cl[u];
for(int v:G[u]) if(w1[v]>d) {
w1[v]=d;
if(cl[u]) q.push_front(v);
else q.push_back(v);
}
}
}
namespace A {
int lim,rd[MAXN],d[MAXN][MAXN/LIM+5];
ll f[MAXN],sum=0;
void bfs4() { //min rounds to every vertex
fill(rd+1,rd+n+1,inf);
deque <int> q;
q.push_back(st[1]),rd[st[1]]=0;
static bool vis[MAXN];
fill(vis+1,vis+n+1,false);
while(q.size()) {
int u=q.front(); q.pop_front();
if(vis[u]) continue; vis[u]=true;
int z=cl[u]&&u!=st[1];
for(int v:G[u]) if(rd[v]>rd[u]+z) {
rd[v]=rd[u]+z;
z?q.push_back(v):q.push_front(v);
}
}
}
void bfs5() { //shortest path to vertex at round d0[u]+i
for(int i=1;i<=n;++i) fill(d[i],d[i]+lim+5,inf);
queue <array<int,2>> q;
q.push({st[1],0}),d[st[1]][0]=0;
while(q.size()) {
int u=q.front()[0],i=q.front()[1]; q.pop();
int z=cl[u]&&d[u][i]>0;
for(int v:G[u]) {
int j=i+rd[u]+z-rd[v];
if(j<=lim&&d[v][j]==inf) d[v][j]=d[u][i]+1,q.push({v,j});
}
}
}
void main() {
lim=n/(m-1),f[1]=2*m-2;
for(int i=2;i<=m;++i) {
ll s=w1[st[i]],t=w2[st[i]]-2; //x+s or 2x+t
if(s!=inf) --f[s-t+1];
sum+=t;
}
for(int i=1;i<=n;++i) f[i]+=f[i-1];
for(int i=1;i<=n;++i) f[i]+=f[i-1];
bfs4(),bfs5(),f[0]=-sum;
for(int i=1;i<=n;++i) for(int j=0;j<=lim;++j) if(d[i][j]!=inf&&j+rd[i]<=n) {
ans[i]=min(ans[i],d[i][j]+f[j+rd[i]]+sum);
}
}
}
namespace B {
int x[MAXN]; //change slope
bool vis[MAXN],chk[MAXN]; //round>1
ll dis[MAXN];
void dijk(int k) {
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
memset(chk,false,sizeof(chk));
priority_queue <array<ll,2>,vector<array<ll,2>>,greater<array<ll,2>>> q;
q.push({dis[st[1]]=0,st[1]});
while(q.size()) {
int u=q.top()[1]; q.pop();
if(vis[u]) continue; vis[u]=true;
int z=(cl[u]&&u!=st[1])?k+1:1;
for(int v:G[u]) if(dis[v]>dis[u]+z) {
q.push({dis[v]=dis[u]+z,v});
chk[v]=chk[u]|(z>1);
}
}
}
void main() {
ll sum=0;
for(int i=2;i<=m;++i) {
int s=w1[st[i]],t=w2[st[i]]-2;
x[i-1]=s-t+1,sum+=t;
}
x[0]=0,x[m]=n,sort(x,x+m+1);
for(int i=0;i<m&&x[i]<n;++i) {
if(i) sum+=x[i]-1;
if(x[i+1]==x[i]) continue;
dijk(2*m-2-i);
for(int u=1;u<=n;++u) if(vis[u]&&chk[u]) ans[u]=min(ans[u],dis[u]+sum);
}
}
}
signed main() {
int E;
scanf("%d%d%d",&n,&E,&m);
for(int u,v;E--;) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
for(int i=1;i<=n;++i) scanf("%1d",&cl[i]);
for(int i=1;i<=m;++i) scanf("%d",&st[i]);
bfs1(),bfs2(),bfs3();
if(m>LIM) A::main();
else B::main();
for(int i=1;i<=n;++i) printf("%lld\n",ans[i]);
return 0;
}
*H. [P10591] Connect
题目大意
给定
张 个点的无向图,求有多少个 的子集满足将这些编号的图的边集异或起来,得到的图连通。 数据范围:
。
思路分析
考虑容斥,钦定得到的图可以分成
那么我们能得到:
其中
那么我们的答案就是
因此我们只要求出
对于每一种方案,就是要钦定集合之间的每条边都在偶数个图里出现过,如果把这些边看成
时间复杂度
一些思考:我们注意到最终
的容斥系数是 ,这个系数在“集合不相等容斥”中似曾相识,他们之间是否有深层的关系? 事实上是有的,我们在“集合不相等容斥”里的系数实际上是:
个点的完全图,对于每一种连通边集 , 的和,我们考虑证明这个值等于 。 我们对
连通边集的限制施加这个容斥,那么 就是分成 个点集后,选出的边集 一定在每个点集内部连边, 的和。 我们发现只要
可选的边 条,其贡献总是会正负抵消,因此 只在 时非零,且此时恰有 ,那么我们就得到了这个容斥系数就是 。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,a[10],g[65][10][10];
ll X[65],fac[65],pw[65],ans=0;
struct bas {
int cnt;
ll b[65];
void init() { cnt=0,memset(b,0,sizeof(b)); }
void ins(ll x) {
for(int i=45;~i;--i) if(x>>i&1) {
if(!b[i]) return b[i]=x,++cnt,void();
x^=b[i];
}
}
} B;
void calc(int k) {
B.init(),memset(X,0,sizeof(X));
for(int i=0;i<n;++i) for(int j=i+1;j<n;++j) if(a[i]!=a[j]) {
for(int o=0;o<m;++o) X[o]=X[o]<<1|g[o][i][j];
}
for(int o=0;o<m;++o) B.ins(X[o]);
ans+=(k&1?1:-1)*fac[k-1]*pw[m-B.cnt];
}
void dfs(int i,int c) {
if(i==n) return calc(c);
for(a[i]=1;a[i]<=c+1;++a[i]) dfs(i+1,max(a[i],c));
}
signed main() {
for(int i=fac[0]=pw[0]=1;i<64;++i) fac[i]=fac[i-1]*i,pw[i]=pw[i-1]*2;
ios::sync_with_stdio(false);
cin>>m;
for(int o=0;o<m;++o) {
string s; cin>>s;
while(n*(n-1)/2!=(int)s.size()) ++n;
for(int i=0,k=0;i<n;++i) for(int j=i+1;j<n;++j) g[o][i][j]=s[k++]-'0';
}
dfs(0,0);
cout<<ans<<"\n";
return 0;
}
*I. [P10064] Path
题目大意
给定
个点的树,在所有简单路径中选择若干条,要求任意两个点之间的路径都存在 条选出的路径覆盖其中的每条边,求方案数。 数据范围:
。
思路分析
显然如果存在一条不合法路径,那么一定存在一条叶子到叶子的不合法路径。
因此只要所有叶子的路径合法。
我们取出每个叶子
注意到
先考虑如何计算
其中
然后考虑
那么我们可以背包维护容斥系数,
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3005,MAXV=5e6+5,MOD=998244353;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll f[MAXN],g[MAXN],C[MAXN][MAXN],pw[MAXV],ans=0;
int n,sz[MAXN],sf[MAXN],lfc=0;
vector <int> G[MAXN];
void dfs(int u,int fz) {
if(G[u].size()==1) return sz[u]=sf[u]=1,void();
for(int v:G[u]) if(v^fz) dfs(v,u);
memset(f,0,sizeof(f)),sz[u]=1,f[0]=1;
int in=0;
for(int v:G[u]) if(v^fz) {
memset(g,0,sizeof(g));
for(int i=0;i<=sf[v];++i) {
ll w=i&1?MOD-C[sf[v]][i]:C[sf[v]][i];
for(int j=0;j<=sf[u];++j) {
g[i+j]=(g[i+j]+w*f[j]%MOD*pw[(sz[v]-i)*(sz[u]-j)])%MOD;
}
}
memcpy(f,g,sizeof(f));
in+=sz[v]*(sz[v]-1)/2;
sf[u]+=sf[v],sz[u]+=sz[v];
}
int oz=n-sz[u],of=lfc-sf[u];
in+=oz*(oz-1)/2;
for(int i=0;i<=sf[u];++i) {
ans=(ans+f[i]*pw[in+(oz-of)*(sz[u]-i)]%MOD*ksm(pw[sz[u]-i]-1,of))%MOD;
}
if(!fz) return ;
in=oz*(oz-1)/2+sz[u]*(sz[u]-1)/2;
for(int i=0;i<=sf[u];++i) {
ll z=i&1?C[sf[u]][i]:MOD-C[sf[u]][i];
ans=(ans+z*pw[in+(oz-of)*(sz[u]-i)]%MOD*ksm(pw[sz[u]-i]-1,of))%MOD;
}
}
signed main() {
scanf("%d",&n);
for(int i=pw[0]=1;i<=n*(n-1)/2;++i) pw[i]=pw[i-1]*2%MOD;
for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) {
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
for(int i=1,u,v;i<n;++i) {
scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
}
if(n==2) return puts("1"),0;
int rt=0;
for(int i=1;i<=n;++i) {
if(G[i].size()==1) ++lfc;
else if(!rt) rt=i;
}
dfs(rt,0);
printf("%lld\n",ans);
return 0;
}
Round #11 - 2024.9.26
A. [P9923] Button
题目大意
给定
网格上的 个点,将其中一些点(至少一个)染黑,使得每行每列黑色格子数奇偶性相同。 数据范围:
。
思路分析
考虑将每行每列拆点建图,那么每个点可以看成两个点之间的一条边,那么我们就是要选出一个非空边集使得在这个边集中每个点的度数奇偶性都相同。
先考虑每个点度数都是偶数,那么选出的边很显然一定会形成若干个欧拉回路,由于度数为
剩余的情况一定是森林,此时我们要求每个点度数均为偶数,自下而上构造,用每个点到父亲的边调整,能得到唯一的方案(或得不到方案)。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,fa[MAXN*2];
int hd[MAXN*2],ec=1,to[MAXN*10],lst[MAXN*10];
bool vis[MAXN*2];
void adde(int u,int v) { to[++ec]=v,lst[ec]=hd[u],hd[u]=ec; }
void dfs1(int u) {
vis[u]=true;
for(int i=hd[u];i;i=lst[i]) if(i!=fa[u]){
int v=to[i];
if(!vis[v]) fa[v]=i^1,dfs1(v);
else {
vector <int> wys{i};
for(int x=u;x!=v;x=to[fa[x]]) wys.push_back(fa[x]);
cout<<"TAK\n"<<wys.size()<<"\n";
for(int z:wys) cout<<z/2<<" ";
cout<<"\n";
exit(0);
}
}
}
bool deg[MAXN*2];
vector <int> wys;
void dfs2(int u) {
vis[u]=true;
for(int i=hd[u];i;i=lst[i]) if(i^fa[u]) dfs2(to[i]);
if(fa[u]&&!deg[u]) {
deg[to[fa[u]]]^=1,wys.push_back(fa[u]),deg[u]^=1;
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1,u,v;i<=m;++i) {
cin>>u>>v,adde(u,v+n),adde(v+n,u);
}
for(int i=1;i<=2*n;++i) if(!vis[i]) dfs1(i);
memset(vis,false,sizeof(vis));
for(int i=1;i<=2*n;++i) if(!vis[i]) {
dfs2(i);
if(!deg[i]) return cout<<"NIE\n",0;
}
cout<<"TAK\n"<<wys.size()<<"\n";
for(int z:wys) cout<<z/2<<" ";
cout<<"\n";
return 0;
}
B. [P10360] Swap
题目大意
给定长度为
的 01 序列 , 次操作 ,如果 就交换 。 一个序列是好的当且仅当最终得到的
序列中的 恰好构成一个区间。 对每个
求有多少恰有 个 的 01 序列是好的,答案对 取模。 数据范围:
。
思路分析
朴素暴力就是对于每个
考虑减小枚举量,对于一个不被任何操作覆盖的
对于一个操作
-
如果
都被确定,那么直接模拟下一步操作。 -
如果
都未被确定,那么我们发现如果 ,那么转移到下一步操作的时候两个序列会得到相同的结果,因此这种序列对答案的贡献一定是偶数,可以忽略掉。因此只要枚举
两种情况。 -
如果仅有
被确定(另一种对称),如果 ,说明这次操作无效,可以跳过,否则我们发现:- 如果
,那么交换后 。 - 如果
,那么交换后 。
因此此时
恒为 , 任取 ,那么直接把 设为已知且值为 , 设为未知即可。 - 如果
容易发现只有第二种情况时状态数翻倍,且此时一定消耗两个未知元素,其他操作不消耗未知元素,那么状态总数就是
时间复杂度
如果
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define q(s,i) (s>>i&1)
#define d(i) (1ll<<i)
using namespace std;
const int MAXN=1005;
int n,m,a[MAXN],b[MAXN];
bool ans[40];
void dfs(int i,ll vis,ll col) {
if(i==m+1) {
for(int l=0;l<n;++l) {
int p=n;
for(int r=l;r<n;++r) if(q(vis,r)&&!q(col,r)) {
p=r; break;
}
for(int r=n-1;r>=l;--r) {
if(r<p) ans[r-l+1]^=1;
if(q(vis,r)&&q(col,r)) break;
}
if(q(vis,l)&&q(col,l)) break;
}
return ;
}
if(q(vis,a[i])&&q(vis,b[i])) {
if(q(col,a[i])&&!q(col,b[i])) col^=d(a[i])^d(b[i]);
dfs(i+1,vis,col);
} else if(q(vis,a[i])) {
if(!q(col,a[i])) dfs(i+1,vis,col);
else {
vis^=d(a[i])^d(b[i]);
col&=~d(a[i]);
dfs(i+1,vis,col|d(b[i]));
}
} else if(q(vis,b[i])) {
if(q(col,b[i])) dfs(i+1,vis,col);
else {
vis^=d(a[i])^d(b[i]);
col&=~d(b[i]);
dfs(i+1,vis,col&(~d(a[i])));
}
} else {
vis|=d(a[i])^d(b[i]);
dfs(i+1,vis,col);
dfs(i+1,vis,col|d(a[i])|d(b[i]));
}
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) scanf("%d%d",&a[i],&b[i]),--a[i],--b[i];
dfs(1,0,0);
for(int i=1;i<=n;++i) printf("%d ",ans[i]); puts("");
return 0;
}
C. [P10367] Flip
题目大意
给定
个点 条边的无向图,节点初始有黑白两种颜色,对于一条两端点同色的边,可以同时翻转两端点颜色,求一共能得到多少种不同的节点颜色组合。 数据范围:
。
思路分析
从链的情况入手,我们发现如果
因此我们能任意排列奇数位置的点和偶数位置的点,并且可以同时增加或减少两种位置上黑点的个数。
因此只要保证两种位置上黑点个数差相等,每种颜色序列都是可以的得到的。
不难将这个结论推广到连通二分图上。
对于一个奇环,我们发现根据上面的过程,可以任意排列整个环的颜色,因此对于一个有奇环的连通块,我们只要求黑点个数的奇偶性不变。
答案就是每个连通块答案的乘积。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll fac[MAXN],ifac[MAXN];
ll C(int x,int y) {
if(x<0||y<0||y>x) return 0;
return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,m,f[2],g[2],w[MAXN];
bool vis[MAXN],col[MAXN],flg;
vector <int> G[MAXN];
void dfs(int u,int c) {
if(vis[u]) return flg&=(col[u]==c),void();
vis[u]=true,col[u]=c,++f[c],g[c]+=w[u];
for(int v:G[u]) dfs(v,c^1);
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=ifac[0]=fac[0]=1;i<=n;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
for(int i=1;i<=n;++i) scanf("%d",&w[i]);
for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
ll ans=1;
for(int u=1;u<=n;++u) if(!vis[u]) {
f[0]=f[1]=g[0]=g[1]=0,flg=1,dfs(u,0);
ll sum=0;
if(flg) {
for(int i=0;i<=f[1];++i) {
sum=(sum+C(f[0],i+g[0]-g[1])*C(f[1],i))%MOD;
}
} else {
int s=f[0]+f[1];
for(int i=g[1]%2;i<=s;i+=2) {
sum=(sum+C(s,i))%MOD;
}
}
ans=ans*sum%MOD;
}
printf("%lld\n",ans);
return 0;
}
D. [P10879] Modify
题目大意
给定
个点的树,其中第 个点的父亲在 中均匀随机, 次操作给 路径边权 ,求出每条 的边的期望边权。 数据范围:
。
思路分析
考虑如何刻画路径边权,考虑一个类似朴素 LCA 的过程,如果
那么可以设计一个简单 dp,
转移就是
算答案就是把每个
对每个
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005,MOD=998244353;
int n,m,l[MAXN],r[MAXN],f[MAXN][MAXN],inv[MAXN],g[MAXN];
int a[MAXN][MAXN],b[MAXN][MAXN];
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void sub(int &x,int y) { x=(x>=y)?x-y:x+MOD-y; }
signed main() {
scanf("%d",&n),inv[1]=1;
for(int i=2;i<=n;++i) scanf("%d%d",&l[i],&r[i]),inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
scanf("%d",&m);
for(int i=1,u,v,w;i<=m;++i) {
scanf("%d%d%d",&u,&v,&w);
add(f[max(u,v)][min(u,v)],w%MOD);
}
for(int j=n;j>=1;--j) {
for(int i=n;i>j;--i) {
add(a[i][j],a[i+1][j]),add(f[i][j],a[i][j]);
add(b[i][j],b[i][j+1]),add(f[i][j],b[i][j]);
add(g[i],f[i][j]);
int z=1ll*f[i][j]*inv[r[i]-l[i]+1]%MOD;
if(j<l[i]) add(a[r[i]][j],z),sub(a[l[i]-1][j],z);
else if(r[i]<j) add(b[j][r[i]],z),sub(b[j][l[i]-1],z);
else {
add(a[r[i]][j],z),sub(a[j][j],z);
add(b[j][j-1],z),sub(b[j][l[i]-1],z);
}
}
}
for(int i=2;i<=n;++i) printf("%d ",g[i]); puts("");
return 0;
}
*E. [P9850] Subgraph
题目大意
给定
个点 条边的无向图,求 子图个数与四元独立集子图个数之差。 数据范围:
。
思路分析
考虑二项式反演,设
对
-
:任选四个点都满足: 。 -
:任选一条边并另选两个点: 。 -
:分讨选的两条边的形态:- 如果有公共顶点,那么枚举顶点并选剩下的一个点:
,其中 表示度数。 - 如果没有公共顶点,任选两条边,容斥掉有公共顶点的情况:
。
- 如果有公共顶点,那么枚举顶点并选剩下的一个点:
-
:分讨选的三条边的形态:- 如果三条边有公共顶点,枚举顶点:
。 - 如果是三元环,答案就是图中三元环个数
乘以 。 - 如果是链,枚举中间的一条边,但我们要求另外两条边的另一个端点不重合,如果重合了就形成了三元环,且一个三元环被算三次:
。
- 如果三条边有公共顶点,枚举顶点:
-
:分讨选的四条边的形态:- 如果是四元环,答案就是图中四元环个数
。 - 如果是三元环外挂一条边,枚举外挂边的节点,答案就是:
,其中 表示覆盖 的三元环个数。
- 如果是四元环,答案就是图中四元环个数
-
:此时只能是两个三元环公用一条边: ,其中 表示过 这条边的三元环个数。
容易发现
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
struct Edge { int v,id; };
vector <Edge> G[MAXN],E[MAXN];
int n,m,deg[MAXN],f[MAXN];
ll c3=0,c4=0,cv[MAXN],ce[MAXN*2];
bool cmp(int x,int y) { return deg[x]<deg[y]||(deg[x]==deg[y]&&x<y); }
ll f0,f1,f2,f3,f4,f5;
signed main() {
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;++i) {
scanf("%d%d",&u,&v),G[u].push_back({v,i}),G[v].push_back({u,i});
++deg[u],++deg[v];
}
for(int u=1;u<=n;++u) for(auto e:G[u]) if(cmp(u,e.v)) E[u].push_back(e);
for(int u=1;u<=n;++u) {
for(auto i:G[u]) for(auto j:E[i.v]) if(cmp(u,j.v)) c4+=f[j.v]++;
for(auto i:G[u]) for(auto j:E[i.v]) f[j.v]=0;
}
for(int u=1;u<=n;++u) {
for(auto i:E[u]) f[i.v]=i.id;
for(auto i:E[u]) for(auto j:E[i.v]) if(f[j.v]) {
++c3,++cv[u],++cv[i.v],++cv[j.v];
++ce[i.id],++ce[j.id],++ce[f[j.v]];
}
for(auto i:E[u]) f[i.v]=0;
}
f0=(__int128)n*(n-1)*(n-2)*(n-3)/24;
f1=1ll*m*(n-2)*(n-3)/2;
f2=1ll*m*(m-1)/2;
for(int i=1;i<=n;++i) f2+=1ll*deg[i]*(deg[i]-1)/2*(n-3-1);
f3=c3*(n-3-3);
for(int u=1;u<=n;++u) f3+=1ll*deg[u]*(deg[u]-1)*(deg[u]-2)/6;
for(int u=1;u<=n;++u) for(auto e:E[u]) f3+=1ll*(deg[u]-1)*(deg[e.v]-1);
f4=c4;
for(int u=1;u<=n;++u) f4+=(deg[u]-2)*cv[u];
for(int i=1;i<=m;++i) f5+=ce[i]*(ce[i]-1)/2;
ll ans=f0-f1+f2-f3+f4-f5;
printf("%lld\n",ans<0?-ans:ans);
return 0;
}
F. [P9878] Pattern
题目大意
给定
网格,有一些位置已经被填 ,构造一种给整个网格填入 的方案,使得其不包含子矩阵 或 。 数据范围:
。
思路分析
先把网格图黑白间隔染色,把被染成黑色的点
首先,如果有一个
对每个
接下来我们任选一个空位,随便填
我们只要说明一次 dfs 中不可能得到
反证法设我们将一个子矩形
也就是形如:
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int n,m,a[105][105];
void dfs(int i,int j) {
if(i<1||j<1||i>=n||j>=m) return ;
int s=a[i][j]+a[i+1][j]+a[i][j+1]+a[i+1][j+1];
if(s!=3&&s!=-3) return ;
s=-s/3;
if(!a[i][j]) a[i][j]=s,dfs(i-1,j-1);
else if(!a[i+1][j]) a[i+1][j]=s,dfs(i+1,j-1);
else if(!a[i][j+1]) a[i][j+1]=s,dfs(i-1,j+1);
else a[i+1][j+1]=s,dfs(i+1,j+1);
}
void solve() {
cin>>n>>m;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
char c; cin>>c,a[i][j]=(c=='?'?0:(c=='B'?1:-1))*((i+j)&1?-1:1);
}
for(int i=1;i<n;++i) for(int j=1;j<m;++j) dfs(i,j);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(!a[i][j]) {
a[i][j]=1,dfs(i-1,j-1),dfs(i-1,j),dfs(i,j-1),dfs(i,j);
}
bool flg=1;
for(int i=1;i<n;++i) for(int j=1;j<m;++j) {
int s=a[i][j]+a[i+1][j]+a[i][j+1]+a[i+1][j+1];
flg&=(s!=4&&s!=-4);
}
if(!flg) return cout<<"NO\n",void();
cout<<"YES\n";
for(int i=1;i<=n;++i,cout<<"\n") for(int j=1;j<=m;++j) {
if((i+j)&1) a[i][j]*=-1;
cout<<(a[i][j]>0?'B':'W');
}
}
signed main() {
ios::sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
*G. [P9924] Signal
题目大意
给定左右各
个点, 条边二分图,给每个点构造长度为 的字符串(字符集 ),使得:
- 两个字符串之间存在至少一位相等当且仅当对应的两个节点属于二分图同一侧,或者有边相连。
- 字符串两两不同。
数据范围:
。
思路分析
一个朴素的做法是对左部点
对于每个右部点,如果其和左部第
第
但此时我们无法保证每个右部点字符串两两不同,我们可以额外用
这样的的串长是
考虑什么样的右部点会得到相同的字符串,当且仅当他们的邻域相同,把这些点称为“等价类”。
注意到只有同一个等价类中的点才需要区分,因此我们可以直接传递其在等价类中排名的二进制表示,而不是
那么假设右部点最大的等价类大小是
考虑把左部点的等价类也划分出来,容易发现一个等价类可以共用一个位置填
那么设左、右部点的等价类数量是
那么花费位数为
综上我们得到的花费位数为
分析一下这个东西的量级,注意到
因此设
当
我们只要考虑
不妨设
考虑左部大小为
如果我们对每个点分别传递连边信息,那么不需要区分标号,但是要
结合一下两种做法,第一位传递
对
综上,我们得到了串长
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=2005;
int n,m,lim,id[MAXN];
vector <int> G[MAXN],lq[MAXN],rq[MAXN];
string s[MAXN];
mt19937_64 rnd(time(0));
ull hs[MAXN],hv[MAXN];
bool vis[MAXN];
int lg(int x) { return __lg(x)+!!(x&(x-1)); }
void solve() {
for(int i=0;i<2*n;++i) {
G[i].clear(),s[i].clear();
lq[i].clear(),rq[i].clear();
vis[i]=false,hs[i]=0,id[i]=-1;
}
for(int i=1,u,v;i<=m;++i) {
cin>>u>>v,--u,--v;
G[u].push_back(v),hs[u]^=hv[v];
G[v].push_back(u),hs[v]^=hv[u];
}
cout<<lim<<"\n";
for(int i=0;i<2*n;++i) s[i]=string(lim,(i<n?'A':'B'));
int lc=0,rc=0,ls=0,rs=0;
for(int i=0;i<n;++i) if(id[i]<0) {
for(int j=i;j<n;++j) if(hs[i]==hs[j]) {
id[j]=lc,lq[lc].push_back(j);
}
ls=max(ls,(int)lq[lc++].size());
}
for(int i=n;i<2*n;++i) if(id[i]<0) {
for(int j=i;j<2*n;++j) if(hs[i]==hs[j]) {
id[j]=rc,rq[rc].push_back(j);
}
rs=max(rs,(int)rq[rc++].size());
}
int len=min(lc,rc)+lg(ls)+lg(rs);
if(len>lim) {
assert(ls==rs&&lc==n-ls+1&&rc==n-rs+1&&(ls==3||ls==5));
int k=0,x=lg(ls),y=lg(rs);
for(int i=0;i<lc;++i) if(lq[i].size()>1) k=i;
if(ls==3) {
//ls=rs=3,lc=rc=n-2
for(int i=0;i<n;++i) if(id[i]!=k) s[i][id[i]]='C';
int a=lq[k][0],b=lq[k][1],c=lq[k][2];
s[a][k]=s[b][k]=s[b][lc]=s[c][lc]='C';
for(int i=n;i<2*n;++i) for(int j:G[i]) {
s[i][id[j]]='C';
if(id[j]==k) s[i][lc]='C';
}
} else {
//ls=rs=5,lc=rc=n-4
for(int i=0;i<n;++i) if(id[i]!=k) s[i][id[i]]='C';
int a=lq[k][0],b=lq[k][1],c=lq[k][2],d=lq[k][3],e=lq[k][4];
s[a][k]=s[b][k]=s[c][k]='C';
s[c][lc]=s[d][lc]=s[e][lc]='C';
s[b][lc+1]=s[d][lc+1]='C';
for(int i=n;i<2*n;++i) for(int j:G[i]) {
s[i][id[j]]='C';
if(id[j]==k) s[i][lc]=s[i][lc+1]='C';
}
}
for(int o=0;o<rc;++o) for(int i=0;i<(int)rq[o].size();++i) {
for(int j=0;j<y;++j) {
s[rq[o][i]][j+lc+x-1]="BC"[i>>j&1];
}
}
} else if(lc<rc) {
for(int i=0;i<n;++i) s[i][id[i]]='C';
for(int i=n;i<2*n;++i) {
for(int j:G[i]) s[i][id[j]]='C';
}
int x=lg(ls),y=lg(rs);
for(int o=0;o<lc;++o) for(int i=0;i<(int)lq[o].size();++i) {
for(int j=0;j<x;++j) {
s[lq[o][i]][j+lc]="AC"[i>>j&1];
}
}
for(int o=0;o<rc;++o) for(int i=0;i<(int)rq[o].size();++i) {
for(int j=0;j<y;++j) {
s[rq[o][i]][j+lc+x]="BC"[i>>j&1];
}
}
} else {
for(int i=n;i<2*n;++i) s[i][id[i]]='C';
for(int i=0;i<n;++i) {
for(int j:G[i]) s[i][id[j]]='C';
}
int x=lg(ls),y=lg(rs);
for(int o=0;o<lc;++o) for(int i=0;i<(int)lq[o].size();++i) {
for(int j=0;j<x;++j) {
s[lq[o][i]][j+rc]="AC"[i>>j&1];
}
}
for(int o=0;o<rc;++o) for(int i=0;i<(int)rq[o].size();++i) {
for(int j=0;j<y;++j) {
s[rq[o][i]][j+rc+x]="BC"[i>>j&1];
}
}
}
for(int i=0;i<2*n;++i) cout<<s[i]<<"\n";
}
signed main() {
for(int i=0;i<MAXN;++i) hv[i]=rnd();
ios::sync_with_stdio(false);
while(cin>>n>>m>>lim) solve();
return 0;
}
*H. [P10197] Minimize
题目大意
给定长度为
的序列 ,给定 个位置不能交换,其他位置上的元素可以任意交换,最小化 。 数据范围:
。
思路分析
从
回到一般的问题,我们设
假设我们在区间
设
注意到关于
因此可以通过调整法证明每个区间内的值域区间
那么就可以考虑在值域上 dp,设可以交换的元素排序后是
根据代价函数的性质进一步分析,我们发现:对于两个值域区间
设
考虑插入区间的形式:
- 合并两个值域区间,枚举
后从 转移。 - 插入一个区间
:- 特判
大小为 的情况,可以钦定这种情况最先考虑,只需要在 时计算,通过合并操作即可转移。 - 否则一定插入
的若干个元素,算出其转移代价后从 转移。
- 特判
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int V=1e6+5;
int a[305],L[10],R[10],len[10],sz[1<<7],w[305],id[10],f[305][305][1<<7];
void chkmin(int &x,int y) { x=x<y?x:y; }
signed main() {
int N,M,n=0,m=0,ans=0;
scanf("%d%d",&N,&M);
for(int i=1;i<=N;++i) scanf("%d",&a[i]),ans+=2*a[i];
for(int i=1;i<=M;++i) scanf("%d",&id[i]);
a[0]=a[N+1]=V,id[0]=0,id[M+1]=N+1,ans+=2*V;
for(int i=0;i<=M;++i) {
if(id[i+1]-id[i]==1) ans+=abs(a[id[i]]-a[id[i+1]]);
else {
L[m]=id[i],R[m]=id[i+1],len[m]=R[m]-L[m]-1;
for(int j=L[m]+1;j<=R[m]-1;++j) w[++n]=a[j];
if(a[L[m]]>a[R[m]]) swap(L[m],R[m]); ++m;
}
}
sort(w+1,w+n+1);
for(int s=0;s<(1<<m);++s) for(int i=0;i<m;++i) if(s>>i&1) sz[s]+=len[i];
memset(f,0x3f,sizeof(f));
for(int i=0;i<=n;++i) f[i+1][i][0]=0;
for(int d=1;d<=n;++d) for(int l=1,r=d;r<=n;++l,++r) for(int s=0;s<(1<<m);++s) if(sz[s]<=d) {
f[l][r][s]=min(f[l][r-1][s],f[l+1][r][s]);
if(sz[s]<d) continue;
for(int j=0;j<m;++j) if(s>>j&1) {
int z=abs(a[L[j]]-w[l])+abs(a[R[j]]-w[r])+w[r]-w[l];
if(len[j]==1&&d==1) {
chkmin(f[l][r][s],z);
} else if(len[j]>1) {
chkmin(f[l][r][s],f[l+1][r-1][s^(1<<j)]+z);
}
}
for(int t=(s-1)&s;t;t=(t-1)&s) {
chkmin(f[l][r][s],f[l][l+sz[t]-1][t]+f[l+sz[t]][r][s^t]);
}
}
printf("%d\n",(ans+f[1][n][(1<<m)-1])/2-2*V);
return 0;
}
Round #12 - 2024.9.27
A. [P10819] Speed
题目大意
给定
个点 条边的图,初始每条边边权为 ,某次操作可以把一条边的边权从 变成 ,进行 次操作,最小化 。 数据范围:
。
思路分析
很显然我们只关心路径交和剩余边的数量。
首先两条路径的路径交肯定是连续的一段,否则把较长的一段调整成较短的即可。
预处理出全源最短路,我们只要枚举路径交的起点终点,就可以求出
对每个
查询时二分斜率,求出最大的
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
const int MAXN=5005;
const ll inf=2e18;
int n,m; ll k;
vector <int> G[MAXN];
int d[MAXN][MAXN],f[MAXN];
ll q1(ll x) {
ll z=sqrt(x)+10;
while(z*(z-1)>x) --z;
return z;
}
ll q2(ll x) {
ll z=sqrt(2*x)+10;
while(z*(z-1)/2>x) --z;
return z;
}
ld qry(ll x,ll z) {
if(!x&&!z) return 0;
ll l=1,r=inf,p=0;
while(l<=r) {
ll mid=(l+r)>>1;
if((q2(mid)-1)*z+(q1(mid)-1)*x<=k) l=mid+1,p=mid;
else r=mid-1;
}
ll s1=q1(p),s2=q2(p),q=k-(s2-1)*z-(s1-1)*x;
ll c1=(s1+1)*s1,c2=(s2+1)*s2/2;
if(c1<c2) {
return (ld)q/(s1+1)+(ld)(x-q)/s1+(ld)z/s2*2;
} else if(c2<c1||q<=z) {
return (ld)x/s1+((ld)(z-q)/s2+(ld)q/(s2+1))*2;
} else {
q-=z;
return (ld)q/(s1+1)+(ld)(x-q)/s1+(ld)z/(s2+1)*2;
}
}
signed main() {
scanf("%d%d%lld",&n,&m,&k);
for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
memset(d,0x0f,sizeof(d));
int s1,t1,s2,t2;
scanf("%d%d%d%d",&s1,&t1,&s2,&t2);
for(int s=1;s<=n;++s) {
queue <int> q;
q.push(s),d[s][s]=0;
while(q.size()) {
int u=q.front(); q.pop();
for(int v:G[u]) if(d[s][v]>d[s][u]+1) d[s][v]=d[s][u]+1,q.push(v);
}
}
memset(f,0x3f,sizeof(f));
f[0]=d[s1][t1]+d[s2][t2];
for(int u=1;u<=n;++u) for(int v=1;v<=n;++v) if(d[u][v]<n) {
f[d[u][v]]=min(f[d[u][v]],d[s1][u]+d[v][t1]+d[s2][u]+d[v][t2]);
f[d[u][v]]=min(f[d[u][v]],d[s1][u]+d[v][t1]+d[s2][v]+d[u][t2]);
}
ld ans=inf;
for(int i=0;i<n;++i) if(f[i]<=4*n) {
ans=min(ans,qry(f[i],i));
}
printf("%.16Lf\n",ans);
return 0;
}
B. [P10200] Xor
题目大意
给定
,将所有数划分成 两个子集,使得 中任意两个不同元素异或和 , 中任意两个不同元素异或和 ,求方案数。 数据范围:
。
思路分析
根据经典结论,如果有
因此我们把
设
考虑
如果
因此我们只要维护数据结构支持整体清空,查询
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7;
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
namespace T {
struct Node {
int s[2],f[2]; bool tg[2];
Node() { s[0]=s[1]=f[0]=f[1]=tg[0]=tg[1]=0; }
} tr[MAXN*64];
void adt(Node &p,int o) { p.tg[o]=true,p.f[o]=0; }
void psd(Node &p) {
for(int o:{0,1}) if(p.tg[o]) {
if(p.s[0]) adt(tr[p.s[0]],o);
if(p.s[1]) adt(tr[p.s[1]],o);
p.tg[o]=false;
}
}
int tot=1;
void ins(ll z) {
int p=1;
for(int i=59;~i;--i) {
int c=z>>i&1;
if(!tr[p].s[c]) tr[p].s[c]=++tot;
p=tr[p].s[c];
}
}
void upd(ll z,int x,int o) {
int p=1;
for(int i=59;~i;--i) {
psd(tr[p]),add(tr[p].f[o],x);
p=tr[p].s[z>>i&1];
}
add(tr[p].f[o],x);
}
int qry(ll z,ll k,int o) {
int p=1,x=0;
for(int i=59;~i;--i) {
psd(tr[p]);
int q=tr[p].s[(z>>i&1)^1];
if(k>>i&1) p=q;
else add(x,tr[q].f[o]),p=tr[p].s[z>>i&1];
}
return x;
}
}
int n;
ll a[MAXN],X,Y;
signed main() {
scanf("%d%lld%lld",&n,&X,&Y),--X,--Y;
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),T::ins(a[i]);
sort(a+1,a+n+1);
ll cur=1ll<<60;
for(int i=2;i<=n;++i) {
int f=(T::qry(a[i],X,0)+(cur>Y))%MOD;
int g=(T::qry(a[i],Y,1)+(cur>X))%MOD;
if((a[i-1]^a[i])<=X) T::adt(T::tr[1],1);
if((a[i-1]^a[i])<=Y) T::adt(T::tr[1],0);
if(f) T::upd(a[i-1],f,1);
if(g) T::upd(a[i-1],g,0);
if(i>1) cur=min(cur,a[i-1]^a[i]);
}
int ans=(T::tr[1].f[0]+T::tr[1].f[1])%MOD;
printf("%d\n",ans);
return 0;
}
C. [P10207] Ball
题目大意
给定
个球,位置为 , 次询问 ,要求从 走到 ,要走到每个球的位置上并拿起所有球,当你拿着 个球时移动速度为 ,求是否能在 时间内到达。 数据范围:
。
思路分析
考虑对
注意到一个球所在位置如果被经过多次,肯定是最后一次经过时拿上最优。
因此每条路径第一次拿起的球一定是
考虑区间 dp,设
转移是容易的,复杂度
查询时枚举起点并找到最接近终点的节点判断答案即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1005,MAXL=5e5+5,inf=1e9;
int n,m,q,cnt[MAXL],a[MAXL];
ll dp[MAXN][MAXN][2][2];
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),++cnt[a[i]];
scanf("%d",&q);
for(int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
sort(a+1,a+n+1),n=unique(a+1,a+n+1)-a-1;
if(n>=1000) {
while(q--) puts("No");
return 0;
}
memset(dp,0x3f,sizeof(dp));
dp[1][n][0][0]=dp[1][n][1][1]=0;
for(int len=n-1;len;--len) for(int l=1,r=len;r<=n;++l,++r) {
ll s=cnt[a[l]-1]+cnt[m]-cnt[a[r]]+1;
for(int x:{0,1}) {
dp[l][r][x][0]=min(dp[l-1][r][x][0]+s*(a[l]-a[l-1]),
dp[l][r+1][x][1]+s*(a[r+1]-a[l]));
dp[l][r][x][1]=min(dp[l-1][r][x][0]+s*(a[r]-a[l-1]),
dp[l][r+1][x][1]+s*(a[r+1]-a[r]));
}
}
for(int s,t,k;q--;) {
scanf("%d%d%d",&s,&t,&k);
ll ans=inf;
if(t<=a[n]) {
int i=lower_bound(a+1,a+n+1,t)-a;
ans=min(ans,dp[i][i][0][0]+abs(s-a[1])+1ll*(cnt[m]+1)*abs(a[i]-t));
ans=min(ans,dp[i][i][1][0]+abs(s-a[n])+1ll*(cnt[m]+1)*abs(a[i]-t));
}
if(a[1]<=t) {
int i=upper_bound(a+1,a+n+1,t)-a-1;
ans=min(ans,dp[i][i][0][0]+abs(s-a[1])+1ll*(cnt[m]+1)*abs(a[i]-t));
ans=min(ans,dp[i][i][1][0]+abs(s-a[n])+1ll*(cnt[m]+1)*abs(a[i]-t));
}
puts(ans+cnt[m]<=k?"Yes":"No");
}
return 0;
}
D. [P10167] Replace
题目大意
给定长度为
的表达式,由数字和 组成,定义 表示把 替换成乘号或者数字串 ,得到的每个表达式的值之和,给定 ,求 。 数据范围:
。
思路分析
考虑单个
注意到
我们可以对于每个位数
此时
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2005,MOD=1e9+7,i10=700000005;
ll ksm(ll a,int b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
char s[MAXN];
int n,m,a[MAXN];
ll L,R,f[MAXN],z[MAXN],dg[MAXN],pw[MAXN],ipw[MAXN],ans,cof[MAXN];
ll eval(int l,int r) { return (dg[r]-dg[l-1]*pw[r]%MOD*ipw[l-1]%MOD+MOD)%MOD; }
ll eval(ll x) {
if(x<0) return 0;
if(x<=n) return z[x];
ll S=0,prd=1;
for(int i=0;i<=n;++i) prd=(x-i)%MOD*prd%MOD;
for(int i=0;i<=n;++i) {
S=(S+prd*ksm((x-i)%MOD)%MOD*cof[i]%MOD*z[i])%MOD;
}
return S;
}
void solve(ll sz) {
ll bas=pow(10,sz),len=ksm(10,sz+1),inv=ksm(len);
for(int q=0;q<=n&&q<bas*9;++q) {
ll o=(bas+q)%MOD;
dg[0]=0,pw[0]=ipw[0]=1;
for(int i=1;i<=n;++i) {
ll d,v,iv;
if(s[i]=='?') d=len,iv=inv,v=o;
else d=10,iv=i10,v=s[i]-'0';
dg[i]=(dg[i-1]*d+v)%MOD;
pw[i]=pw[i-1]*d%MOD;
ipw[i]=ipw[i-1]*iv%MOD;
}
memset(f,0,sizeof(f)),f[0]=1;
ll s1=1,s2=0;
for(int i=1;i<=m;++i) {
f[i]=(s1*dg[a[i]-1]+(MOD-s2)*pw[a[i]-1])%MOD;
s1=(s1+f[i])%MOD;
s2=(s2+f[i]*dg[a[i]]%MOD*ipw[a[i]])%MOD;
}
z[q]=f[m];
}
for(int i=1;i<=n;++i) z[i]=(z[i]+z[i-1])%MOD;
if(R>=bas) ans=(ans+eval(min(10*bas-1,R)-bas))%MOD;
if(L-1>=bas) ans=(ans+MOD-eval(min(10*bas-1,L-1)-bas))%MOD;
}
signed main() {
scanf("%s%lld%lld",s+1,&L,&R),n=strlen(s+1);
for(int i=0;i<=n;++i) {
cof[i]=1;
for(int j=0;j<=n;++j) if(i!=j) cof[i]=cof[i]*(i+MOD-j)%MOD;
cof[i]=ksm(cof[i])%MOD;
}
for(int i=1;i<=n;++i) if(s[i]=='?') a[++m]=i;
a[++m]=n+1;
for(int i=0;i<18;++i) solve(i);
printf("%lld\n",ans);
return 0;
}
E. [P11106] Divide
题目大意
给定
阶排列 ,分成两个子序列 ,最大化 中 Local-Max 个数加上 中 Local-Min 个数。 数据范围:
。
思路分析
考虑
在 std::set
对每个
然后枚举
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN],pre[MAXN],suf[MAXN],f[MAXN],w[MAXN];
struct FenwickTree1 {
int tr[MAXN],s;
void init() { fill(tr,tr+n+1,0); }
void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]=max(tr[x],v); }
int qry(int x) { for(s=0,x=min(x,n);x;x&=x-1) s=max(s,tr[x]); return s; }
} T1;
struct FenwickTree2 {
int tr[MAXN],s;
void init() { fill(tr,tr+n+1,0); }
void add(int x,int v) { for(;x;x&=x-1) tr[x]=max(tr[x],v); }
int qry(int x) { for(s=0,x=max(x,1);x<=n;x+=x&-x) s=max(s,tr[x]); return s; }
} T2;
void solve() {
scanf("%d",&n); int ans=0;
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
set <int> S{0,n+1}; f[0]=f[n+1]=0;
for(int i=1;i<=n;++i) {
auto it=--S.upper_bound(a[i]);
w[i]=f[a[i]]=++f[*it],pre[i]=*it,suf[i]=*++it,S.insert(a[i]);
}
T1.init(),T2.init();
for(int i=n;i>=1;--i) {
ans=max(ans,w[i]+T1.qry(pre[i])+T2.qry(pre[i]));
ans=max(ans,w[i]+T1.qry(suf[i])+T2.qry(suf[i]));
T1.add(a[i],T1.qry(a[i])+1),T2.add(a[i],T2.qry(a[i])+1);
}
printf("%d\n",ans);
}
signed main() {
int Q; scanf("%d",&Q);
while(Q--) solve();
return 0;
}
F. [P11111] Range
题目大意
给定
个点的树,每个点点权 有一个限定范围 , 组询问给定 ,要求构造一组 使得树上最大权独立集为 。 数据范围:
。
思路分析
考虑所有
然后考虑每次令某个
那么
容易发现一定存在某个时刻
那么我们求出
如果按
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,q,dcnt;
vector <int> G[MAXN];
ll X,Y,M;
ll l[MAXN],r[MAXN],s[MAXN],hv[MAXN],rk[MAXN],p[MAXN],t[MAXN];
array<ll,2> f[MAXN],g[MAXN];
ll hsh(int i,ll z) { return (i*X+z*z%M*Y)%M; }
void dfs1(int u,int fz) {
f[u]={0,l[u]},g[u]={0,r[u]};
for(int v:G[u]) if(v^fz) {
dfs1(v,u);
f[u][0]+=max(f[v][0],f[v][1]);
g[u][0]+=max(g[v][0],g[v][1]);
f[u][1]+=f[v][0];
g[u][1]+=g[v][0];
}
}
void dfs2(int u,int fz,array<ll,2>o) {
rk[++dcnt]=u,hv[dcnt]=hv[dcnt-1]^hsh(u,l[u])^hsh(u,r[u]);
o[0]+=f[u][0],o[1]+=f[u][1]+r[u]-l[u],s[dcnt]=max(o[0],o[1]);
for(int v:G[u]) if(v^fz) {
o[0]-=max(f[v][0],f[v][1]),o[1]-=f[v][0];
dfs2(v,u,{max(o[0],o[1]),o[0]});
o[0]+=max(g[v][0],g[v][1]),o[1]+=g[v][0];
}
}
void solve() {
scanf("%d%d%lld%lld%lld",&n,&q,&X,&Y,&M);
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1,u,v;i<n;++i) {
scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=n;++i) scanf("%lld%lld",&l[i],&r[i]);
dfs1(1,0);
s[0]=max(f[1][0],f[1][1]),hv[0]=0;
for(int i=1;i<=n;++i) hv[0]^=hsh(i,l[i]);
dcnt=0,dfs2(1,0,{0,0});
for(int i=1;i<=q;++i) scanf("%lld",&p[i]);
for(int i=1;i<=q;++i) {
if(p[i]<s[0]||p[i]>s[n]) printf("-1 ");
else {
int j=lower_bound(s,s+n+1,p[i])-s,u=rk[j];
ll k=s[j]-p[i],ans=hv[j]^hsh(u,r[u])^hsh(u,r[u]-k);
printf("%lld ",ans);
}
}
puts(""),fflush(stdout);
while(true) {
int o; scanf("%d",&o);
if(!o) break;
if(p[o]<s[0]||p[o]>s[n]) puts("-1");
else {
int j=lower_bound(s,s+n+1,p[o])-s,u=rk[j];
for(int i=1;i<=j;++i) t[rk[i]]=r[rk[i]];
for(int i=j+1;i<=n;++i) t[rk[i]]=l[rk[i]];
t[u]-=s[j]-p[o];
for(int i=1;i<=n;++i) printf("%lld ",t[i]);
puts("");
}
fflush(stdout);
}
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*G. [P9983] Cover
题目大意
给定一棵
个点的树,有一些点是黑色, 次询问 ,求最小的 ,使得将距离 中点 的点全部染黑后,每个点的着色情况恰好和题目给出的一致。 数据范围:
。
思路分析
先考虑所有点是黑色的情况。
从最深的点
显然考虑
然后考虑一般的情况,设一个点
还是从最深的点
这样的
不妨尝试找最浅的
那么设
但是此时随着
但我们发现如果设
因此
不难发现此时
而
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,lim,pri[MAXN],dep[MAXN],dfn[MAXN],dcnt,st[MAXN][20],up[MAXN][20];
char op[MAXN];
vector <int> G[MAXN];
void bfs1() {
queue <int> q;
memset(pri,0x3f,sizeof(pri));
for(int i=1;i<=n;++i) if(op[i]=='0') pri[i]=0,q.push(i);
while(q.size()) {
int u=q.front(); q.pop();
for(int v:G[u]) if(pri[v]>pri[u]+1) q.push(v),pri[v]=pri[u]+1;
}
}
void dfs0(int u,int fz) {
dfn[u]=++dcnt,dep[u]=dep[fz]+1,st[dcnt][0]=up[u][0]=fz;
for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
for(int v:G[u]) if(v^fz) dfs0(v,u);
}
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int dist(int x,int y) {
if(x==y) return 0;
int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
return dep[x]+dep[y]-2*dep[cmp(st[l][k],st[r-bit(k)+1][k])];
}
int fa[MAXN],siz[MAXN],cur[MAXN];
bool vis[MAXN];
void dfs1(int u) {
vis[u]=true;
function<void(int,int)> dfs2=[&](int x,int fz) {
siz[x]=1;
for(int y:G[x]) if(y!=fz&&!vis[y]) dfs2(y,x),siz[x]+=siz[y];
};
dfs2(u,0);
for(int v:G[u]) if(!vis[v]) {
int rt=0;
function<void(int,int)> dfs3=[&](int x,int fz) {
cur[x]=siz[v]-siz[x];
for(int y:G[x]) if(y!=fz&&!vis[y]) dfs3(y,x),cur[x]=max(cur[x],siz[y]);
if(!rt||cur[rt]>cur[x]) rt=x;
};
dfs3(v,u),fa[rt]=u,dfs1(rt);
}
}
int d[MAXN],ver[MAXN],rd[MAXN];
void bfs2() {
queue <int> q; memset(d,0x3f,sizeof(d));
for(int i=1;i<=n;++i) if(pri[i]>lim) d[i]=0,ver[i]=i,q.push(i);
while(q.size()) {
int u=q.front(); q.pop();
for(int v:G[u]) if(d[v]>d[u]+1) ver[v]=ver[u],d[v]=d[u]+1,q.push(v);
}
}
void upd(int x) { for(int u=x;u;u=fa[u]) rd[u]=max(rd[u],lim-dist(x,u)); }
bool qry(int x) { for(int u=x;u;u=fa[u]) if(rd[u]>=dist(u,x)) return true; return false; }
vector <int> ord;
void solve() {
scanf("%d",&lim),bfs2();
memset(rd,-0x3f,sizeof(rd));
int ans=0;
for(int u:ord) {
if(qry(u)) continue;
if(d[u]>lim) return puts("-1"),void();
int v=u;
for(int k=19;~k;--k) if(up[v][k]&&d[up[v][k]]+dep[u]-dep[up[v][k]]<=lim) v=up[v][k];
upd(ver[v]),++ans;
}
printf("%d\n",ans);
}
signed main() {
int T;
scanf("%d%s",&n,op+1);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
bfs1(),dfs0(1,0);
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
}
for(int i=1;i<=n;++i) if(op[i]=='1') ord.push_back(i);
sort(ord.begin(),ord.end(),[&](int x,int y){ return dep[x]>dep[y]; });
dfs1(1),scanf("%d",&T);
while(T--) solve();
return 0;
}
*H. [P11107] Mex
题目大意
给定集合
,定义 表示将 中所有数异或上 后得到的集合。
组询问给定 ,求有多少 满足:
且 。 - 序列
最终收敛于 ,且 。 数据范围:
。
思路分析
设
- 如果
不全在 中,那么 的极限等于 在 中的极限。 - 否则
至少是 :- 如果
在 中,那么 至少是 ,翻转一次后变成 在 中的极限。 - 否则
:- 如果
,那么翻转后 依然全满, 依然不属于 ,此时极限就是 。 - 否则翻转后变成
的问题,答案是 在 范围内翻转低 位后的极限。
- 如果
- 如果
容易发现此时
考虑 dp 刻画,
但我们无法处理翻转后的情况,注意到翻转后我们要处理的子问题就是一个钦定
边界条件是
考虑一般的情况,先处理第一种情况,即
可以用 NTT 优化,容易把对
然后考虑剩余后面的情况,注意翻转的情况仅在
回答时特判
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int N=1<<18;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,m;
inline void init(ull B) { b=B,m=ull((uLL(1)<<64)/B); }
inline ull mod(ull a) {
ull q=((uLL(m)*a)>>64),r=a-q*b;
return r>=b?r-b:r;
}
}
#define o(x) FastMod::mod(x)
int MOD,G;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
vector <int> fr;
for(int i=2,z=MOD-1;i*i<=z;++i) if(z%i==0) fr.push_back(i),fr.push_back(z/i);
for(G=2;;++G) {
bool ok=true;
for(int z:fr) if(ksm(G,z)==1) { ok=false; break; }
if(ok) break;
}
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=o(1ll*(MOD-MOD/i)*inv[MOD%i]);
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=o(1ll*fac[i-1]*i),ifac[i]=o(1ll*ifac[i-1]*inv[i]);
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=o(1ll*x*w[i+k-1]);
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=o(1ll*f[j+k/2]*w[k+j-i]);
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=o(1ll*f[i]*x);
}
}
int f[18][18][N],g[18][18][N],a[N],b[N];
int C(int x,int y) { return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
ios::sync_with_stdio(false);
cin>>MOD,FastMod::init(MOD),poly_init();
for(int i=1;i<18;++i) {
int s=1<<(i-1),len=s<<1;
memset(a,0,sizeof(int)*len);
for(int k=0;k<s;++k) a[k]=C(s-1,k);
ntt(a,0,len);
for(int j=0;j<i-1;++j) {
int *F=f[i][j],*G=g[i][j];
memset(b,0,sizeof(int)*len);
for(int k=0;k<s;++k) b[k]=f[i-1][j][k];
ntt(b,0,len);
for(int k=0;k<len;++k) b[k]=o(1ll*a[k]*b[k]);
ntt(b,1,len);
for(int k=0;k<len;++k) add(G[k],b[k]),add(F[k],b[k]),add(F[k+1],b[k]);
for(int k=0;k<s;++k) {
add(F[k+s],f[i-1][j][k]),add(F[k+s],g[i-1][j][k]);
add(G[k+s],g[i-1][j][k]);
}
}
if(i==1) f[1][0][1]=g[1][0][1]=1;
else for(int k=0;k<s-1;++k) f[i][i-1][k+s]=g[i][i-1][k+s]=C(s-2,k);
}
int T; cin>>T;
for(int k,n,p;T--;) {
cin>>k>>n>>p;
if(n==(1<<k)) cout<<(p==n)<<"\n";
else cout<<(p==(p&-p)?f[k][__lg(p)][n]:0)<<"\n";
}
return 0;
}
*I. [P9884] Ant
题目大意
给定
个洞口,每个洞口每秒至多进或出一只蚂蚁。 第
秒从洞口爬出的蚂蚁会在第 秒到达冰箱,第 秒进入洞口的蚂蚁至少要在 秒前到达冰箱,求至少多少秒后 只蚂蚁各经过冰箱一次。 数据范围:
。
思路分析
考虑二分时间
如果我们知道每个洞口每个时刻是出蚂蚁还是进蚂蚁,那么一对合法的起点终点可以看成一条边,最多完成的蚂蚁数量就是二分图上的最大匹配。
由于左部每个点连的都是右部一段严格递减的后缀,因此可以贪心匹配每个右部点。
我们只要解决每个洞口是出蚂蚁还是进蚂蚁的问题。
注意到我们可以倒转时间使得进、出蚂蚁的事件交换,根据对称性不难猜测一定是前一半时间出蚂蚁,后一半时间进蚂蚁最优。
那么左、右部点分别是
唯一的细节是
注意到所有差分数组都是
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
const ll inf=2.5e14;
int n,a[MAXN];
ll m;
struct opr {
ll x; int c,op;
friend bool operator <(const opr &x,const opr &y) { return x.x<y.x; }
} s[MAXN*6];
bool chk(ll T) {
int p=0;
if(T&1) {
for(int i=1;i<=n;++i) s[++p]={a[i]+1,2,0};
for(int i=1;i<=n;++i) s[++p]={a[i]+T/2+1,-1,0};
for(int i=1;i<=n;++i) s[++p]={a[i]+T/2+2,-1,0};
for(int i=n;i>=1;--i) s[++p]={T/2-a[i],1,1};
for(int i=n;i>=1;--i) s[++p]={T/2-a[i]+1,1,1};
for(int i=n;i>=1;--i) s[++p]={T-a[i],-2,1};
for(int o=1;o<6;++o) inplace_merge(s+1,s+o*n+1,s+(o+1)*n+1);
} else {
for(int i=1;i<=n;++i) s[++p]={a[i]+1,2,0};
for(int i=1;i<=n;++i) s[++p]={a[i]+T/2+1,-2,0};
for(int i=n;i>=1;--i) s[++p]={T/2-a[i],2,1};
for(int i=n;i>=1;--i) s[++p]={T-a[i],-2,1};
for(int o=1;o<4;++o) inplace_merge(s+1,s+o*n+1,s+(o+1)*n+1);
}
ll ans=0,tot=0,lx=0,rx=0;
for(int i=1;i<p;++i) {
(s[i].op?rx:lx)+=s[i].c;
ll d=s[i+1].x-s[i].x;
if(lx>=rx) ans+=rx*d,tot+=(lx-rx)*d;
else {
ans+=lx*d;
ll w=min((rx-lx)*d,tot);
tot-=w,ans+=w;
}
}
return ans>=2*m;
}
void solve() {
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
sort(a+1,a+n+1);
ll l=0,r=inf,z=inf;
while(l<=r) {
ll mid=(l+r)>>1;
if(chk(mid)) z=mid,r=mid-1;
else l=mid+1;
}
printf("%lld\n",z);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧