[[https://codeforces.com/group/2g1PZcsgml/contest/337661|比赛链接]]
====== 题解 ======
===== C. Cover the Paths =====
==== 题意 ====
给定一棵树和若干路径,要求选出最小的点集,使得每条路径上至少有一个点。
==== 题解 ====
强制以 $1$ 为根,根据每条路径两端点的 $\text{LCA}$ 深度从大到小排序。
对于 $\text{LCA}$ 深度最大的路径,显然必须选择一个点,由于其他路径的 $\text{LCA}$ 深度都不小于该路径,显然选择该路径的 $\text{LCA}$ 最优。
然后再考虑深度第二大的点,如果该路径上已经有点被选就不再选点,否则再选一个 $\text{LCA}$,不断贪心。
贪心正确性是由于该决策顺序下路径 $\text{LCA}$ 深度递增,所以之前选的点深度越小越好,因此前面的路径的点只选 $\text{LCA}$,且尽量少选。
const int MAXN=1e5+5;
#define lowbit(x) ((x)&(-x))
namespace Tree{
int c[MAXN];
void add(int pos){
while(posmson[u]){
h_son[u]=v;
mson[u]=sz[v];
}
}
}
void dfs_2(int u,int top){
dfn[u]=++dfs_t,p[u]=top;
if(mson[u])
dfs_2(h_son[u],top);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f[u]||v==h_son[u])
continue;
dfs_2(v,v);
}
}
int query(int u,int v){
int ans=0;
while(p[u]!=p[v]){
if(d[p[u]]d[v])
swap(u,v);
ans+=Tree::query(dfn[u],dfn[v]);
return ans;
}
int lca(int u,int v){
while(p[u]!=p[v]){
if(d[p[u]]d[b.p];
}
}node[MAXN];
int main(){
int n=read_int();
_for(i,1,n){
int u=read_int(),v=read_int();
Insert(u,v);
Insert(v,u);
}
dfs_1(1,0,0);
dfs_2(1,1);
int m=read_int();
_for(i,0,m){
int u=read_int(),v=read_int(),p=lca(u,v);
node[i]=Node{u,v,p};
}
sort(node,node+m);
vector ans;
_for(i,0,m){
if(query(node[i].u,node[i].v)==0){
ans.push_back(node[i].p);
Tree::add(dfn[node[i].p]);
}
}
enter(ans.size());
for(int u:ans)
space(u);
return 0;
}
===== G. Berland Post =====
==== 题意 ====
给定若干条形如 $x_v+b_i\le x_u+T$ 的限制,求最小的 $T$ 以及这种情况下的一组合法解 $(x_1,x_2\cdots x_n)$。
其中,某些 $x_i$ 的值已经固定。
==== 题解 ====
将方程转化为 $x_v\le x_u+T-b_i$,考虑二分 $T$ 然后差分约束求解。
设超级源点为 $s$。关于点权的限制,对于无限制的 $x_i$,连边 $(s,i,\infty),(i,s,\infty)$,表示 $-\infty\le x_i\le \infty$。
对于固定的 $x_i$,连边 $(s,i,v),(i,s,-v)$,表示 $v\le x_i\le v$。时间复杂度 $O(nm\log V)$。
const int MAXN=1e3+5,MAXM=4e3+5,SPV=2e5;
const double inf=1e15,infv=1e7,eps=1e-7,eps2=1e-9;
int read_int(){
int t=0;bool sign=false;char c=getchar();
while(c==' '||c=='\r'||c=='\n')c=getchar();
if(c=='?')return SPV;
if(c=='-')sign=true,c=getchar();
while(isdigit(c)){
t=t*10+c-'0';
c=getchar();
}
return sign?-t:t;
}
struct Edge{
int to,next;
double w;
}edge[MAXM<<1];
int head[MAXN],edge_cnt;
void Insert(int u,int v,double w){
++edge_cnt;
edge[edge_cnt].to=v;
edge[edge_cnt].w=w;
edge[edge_cnt].next=head[u];
head[u]=edge_cnt;
}
namespace SPFA{
double dis[MAXN];
int len[MAXN];
bool inque[MAXN];
bool solve(int src,int n){
queueq;
_rep(i,1,n){
inque[i]=false;
len[i]=0;
dis[i]=inf;
}
dis[src]=0;len[src]=1;
q.push(src);
inque[src]=true;
while(!q.empty()){
int u=q.front();q.pop();
inque[u]=false;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].w+eps2){
dis[v]=dis[u]+edge[i].w;
len[v]=len[u]+1;
if(len[v]>n)
return false;
if(!inque[v]){
q.push(v);
inque[v]=true;
}
}
}
}
return true;
}
}
struct Edge2{
int u,v,w;
};
vector edges;
int a[MAXN];
bool check(int n,double T){
int s=++n;
edge_cnt=0;
_rep(i,1,n)
head[i]=0;
_for(i,1,n){
if(a[i]==SPV){
Insert(s,i,infv);
Insert(i,s,infv);
}
else{
Insert(s,i,a[i]);
Insert(i,s,-a[i]);
}
}
for(Edge2 e:edges)
Insert(e.v,e.u,T-e.w);
return SPFA::solve(s,n);
}
int main(){
int n,m;
while(cin>>n>>m){
edges.clear();
_rep(i,1,n)
a[i]=read_int();
while(m--){
int u=read_int(),v=read_int(),w=read_int();
edges.push_back(Edge2{u,v,w});
}
double lef=0,rig=infv;
while(rig-lef>eps){
double mid=(lef+rig)/2;
if(check(n,mid))
rig=mid;
else
lef=mid;
}
printf("%.6lf\n",lef);
check(n,lef);
_rep(i,1,n)
printf("%.6lf ",SPFA::dis[i]);
puts("");
}
return 0;
}
===== J. Subsequence Sum Queries =====
==== 题意 ====
给定一个长度为 $n$ 的序列,接下来 $q$ 个询问,每次询问区间 $[l,r]$ 有多少个子序列满足元素之和整除 $m$。
==== 题解 ====
考虑分治处理询问。设当前维护区间为 $[lef,rig]$,$mid=\frac {lef+rig}2$。对 $[l,r]\in [lef,mid],[mid+1,rig]$ 的询问直接递归到左右区间处理。
对于跨 $\text{mid}$ 的询问,提前 $O\left((rig-lef)m\right)$ 处理出区间 $[i,mid](lef\le i\le mid),[mid+1,j](mid+1\le j\le rig)$ 的答案。
然后对每个询问相当于背包合并,时间复杂度 $O(qm^2)$。于是总时间复杂度 $O(nm\log n+qm^2)$。
const int MAXN=2e5+5,MAXM=20,mod=1e9+7;
int ans[MAXN],a[MAXN],m;
struct query{
int lef,rig,id;
};
int s[MAXN][MAXM],temp[MAXM<<1];
void solve(int lef,int rig,vector b){
int mid=lef+rig>>1;
if(lef==rig){
_for(i,0,b.size())
ans[b[i].id]=1+(a[mid]==0);
return;
}
mem(s[mid],0);
s[mid][0]++;
s[mid][a[mid]]++;
for(int i=mid-1;i>=lef;i--){
_for(j,0,m)
s[i][(a[i]+j)%m]=(s[i+1][(a[i]+j)%m]+s[i+1][j])%mod;
}
mem(s[mid+1],0);
s[mid+1][0]++;
s[mid+1][a[mid+1]]++;
for(int i=mid+2;i<=rig;i++){
_for(j,0,m)
s[i][(a[i]+j)%m]=(s[i-1][(a[i]+j)%m]+s[i-1][j])%mod;
}
vectorb1,b2;
_for(i,0,b.size()){
if(b[i].rig<=mid)
b1.push_back(b[i]);
else if(b[i].lef>mid)
b2.push_back(b[i]);
else{
mem(temp,0);
_for(j,0,m)_for(k,0,m)
temp[j+k]=(temp[j+k]+1LL*s[b[i].lef][j]*s[b[i].rig][k])%mod;
ans[b[i].id]=(temp[0]+temp[m])%mod;
}
}
solve(lef,mid,b1);
solve(mid+1,rig,b2);
}
int main()
{
int n=read_int();
m=read_int();
_rep(i,1,n)a[i]=read_LL()%m;
int q=read_int();
vector b;
_for(i,0,q){
int l=read_int(),r=read_int();
b.push_back(query{l,r,i});
}
solve(1,n,b);
_for(i,0,q)
enter(ans[i]);
return 0;
}
===== L. Increasing Costs =====
==== 题意 ====
给定一个无向连通图,定义源点为 $1$ 号点。对每条边,询问删除该边会导致源点到多少个点的最短路改变。
==== 题解 ====
首先跑最短路,然后保留所有在最短路树上的边,同时规定每条边方向由距离近的点指向距离远的点,易知构成有向无环图。
问题转化为求有向无环图的支配边。
对任意一个点,如果该点有至少两条入边,易知所有入边支配点集均为空,否则该边的支配点集等价于该点的支配子树。
const int MAXN=2e5+5,MAXM=2e5+5,MAXV=22;
namespace Tree{
struct Edge{
int to,id,next;
}edge[MAXN+MAXM];
int head1[MAXN],head2[MAXN],edge_cnt;
int deg[MAXN],f[MAXN],dep[MAXN],anc[MAXN][MAXV],lg2[MAXN];
int deg0[MAXN];
void Insert1(int u,int v,int id){
edge[++edge_cnt]=Edge{v,id,head1[u]};
head1[u]=edge_cnt;
deg[v]++;
deg0[v]++;
}
void Insert2(int u,int v){
edge[++edge_cnt]=Edge{v,0,head2[u]};
head2[u]=edge_cnt;
}
int LCA(int u,int v){
if(dep[u]dep[v])u=anc[u][lg2[dep[u]-dep[v]]];
if(u==v)
return u;
for(int i=MAXV-1;i>=0;i--){
if(anc[u][i]!=anc[v][i])
u=anc[u][i],v=anc[v][i];
}
return anc[u][0];
}
int build(int n){
lg2[1]=0;
_for(i,2,MAXN)lg2[i]=lg2[i>>1]+1;
int rt=n+1;
queue q;
_rep(i,1,n){
if(deg[i]==0){
f[i]=rt;
q.push(i);
}
}
while(!q.empty()){
int u=q.front();q.pop();
dep[u]=dep[f[u]]+1;
Insert2(f[u],u);
anc[u][0]=f[u];
for(int i=1;i >q;
mem(dis,127);
dis[1]=0;
q.push(make_pair(-dis[1],1));
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u])continue;
vis[u]=true;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].w){
dis[v]=dis[u]+edge[i].w;
q.push(make_pair(-dis[v],v));
}
}
}
_rep(u,1,n){
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v]==dis[u]+edge[i].w)
Tree::Insert1(u,v,edge[i].id);
}
}
Tree::solve(n,m);
return 0;
}
====== 赛后总结 ======
jxm:开局一个多小时后就罚坐了,疯狂写假题,下次一定先确认思路没问题再写题。