专项测试1
A. 序列
写的在线分块做法 复杂度 \(O(n\sqrt{n}\log{n})\)
对每个块都先排序,散块先 \(pushdown\) 再暴力处理,最后排序重构
区间赋最大值的整块操作,可以二分出来一个操作的位置,前面的都需要修改
于是可以差分一下,在 \(pushdown\) 的时候还原再修改回去
赛时的写法常数飞起,随机数据甚至没跑过暴力就弃掉了,赛后换了个常数小点的就过了
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m;
int cc[100010],mx[100010];
int bl[100010],blo;
char st[10];
struct node{
int v,c,id;
inline bool operator<(const node &b)const{return v<b.v;}
}aa[100010];
struct BLOCK{
int atag,ctag;
int l,r,lstp,lstv;
inline void pushdown(){
for(int i=l+1;i<=r;i++) cc[i]+=cc[i-1];
for(int i=r-1;i>=l;i--) mx[i]=max(mx[i],mx[i+1]);
for(int i=l;i<=r;i++){
if(mx[i]!=-inf) aa[i].v=mx[i];
aa[i].v+=atag,aa[i].c+=ctag,aa[i].c+=cc[i];
}
}
inline void rebuild(){
atag=0,ctag=0;lstp=l;lstv=-inf;
for(int i=l;i<=r;i++) cc[i]=0,mx[i]=-inf;
sort(aa+l,aa+r+1);
}
inline void getmx(int k){
k-=atag;
if(k<=lstv) return;
int pos=upper_bound(aa+lstp,aa+r+1,(node){k-1,0,0})-aa-1;
if(pos>=lstp){
lstp=pos;cc[l]++;
if(pos<r) cc[pos+1]--;
mx[pos]=max(mx[pos],k);
lstv=k;
}
}
}B[510];
inline void upd(int l,int r,int k){
if(bl[l]==bl[r]){
B[bl[l]].pushdown();
for(int i=B[bl[l]].l;i<=B[bl[l]].r;i++) if(aa[i].id>=l&&aa[i].id<=r) aa[i].v+=k,aa[i].c++;
B[bl[l]].rebuild();
return ;
}
B[bl[l]].pushdown();B[bl[r]].pushdown();
for(int i=B[bl[l]].l;i<=B[bl[l]].r;i++) if(aa[i].id>=l&&aa[i].id<=r) aa[i].v+=k,aa[i].c++;
for(int i=B[bl[r]].l;i<=B[bl[r]].r;i++) if(aa[i].id>=l&&aa[i].id<=r) aa[i].v+=k,aa[i].c++;
for(int i=bl[l]+1;i<bl[r];i++) B[i].atag+=k,B[i].ctag++;
B[bl[l]].rebuild();B[bl[r]].rebuild();
}
inline void getmx(int l,int r,int k){
if(bl[l]==bl[r]){
B[bl[l]].pushdown();
for(int i=B[bl[l]].l;i<=B[bl[l]].r;i++) if(aa[i].id>=l&&aa[i].id<=r) if(aa[i].v<k) aa[i].v=k,aa[i].c++;
B[bl[l]].rebuild();
return ;
}
B[bl[l]].pushdown();B[bl[r]].pushdown();
for(int i=B[bl[l]].l;i<=B[bl[l]].r;i++) if(aa[i].id>=l&&aa[i].id<=r) if(aa[i].v<k) aa[i].v=k,aa[i].c++;
for(int i=B[bl[r]].l;i<=B[bl[r]].r;i++) if(aa[i].id>=l&&aa[i].id<=r) if(aa[i].v<k) aa[i].v=k,aa[i].c++;
for(int i=bl[l]+1;i<bl[r];i++) B[i].getmx(k);
B[bl[l]].rebuild();B[bl[r]].rebuild();
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
n=read();blo=200;
for(int i=1;i<=n;i++) aa[i].v=read(),aa[i].id=i;
for(int i=1;i<=n;i++) bl[i]=(i-1)/blo+1;
for(int i=1;i<=bl[n];i++) B[i].l=(i-1)*blo+1,B[i].r=min(n,i*blo);
for(int i=1;i<=bl[n];i++) B[i].rebuild();
m=read();
for(int i=1,l,r,k;i<=m;i++){
scanf("%s",st+1);
if(st[1]=='A'){l=read(),r=read(),k=read();if(k) upd(l,r,k);}
if(st[1]=='M'){l=read(),r=read(),k=read();getmx(l,r,k);}
if(st[1]=='Q'){k=read();B[bl[k]].pushdown();for(int i=B[bl[k]].l;i<=B[bl[k]].r;i++) if(aa[i].id==k) printf("%lld %lld\n",aa[i].v,aa[i].c);B[bl[k]].rebuild();}
}
return 0;
}
B. 旅行计划
如果 \(u,v\) 不在一个块里直接输出 \(\text{NIE}\)
发现最后的答案一定为 \(gcd(K,l_1,l_2,...)\) 其中 \(l\) 为边的边权
简单意会一下 一堆数加加减减再模一模他们的 \(gcd\) 不变
所以可以将所有边权先除一个 \(gcd\) 最后再乘回来
如果 \(K\) 为奇数的话直接输出 \(0\) 你可以在 \((u,v)\) 之间来回走 \(K\) 次使得答案为 \(0\)
那么只用考虑偶数的情况
根据裴蜀定理一定存在一种解使得 \(k_1l_1+k_2l_2+...+k_KK=1\)
然后在模 \(K\) 意义下 一定存在 \(k_1l_1+k_2l_2+...+k_nl_n\equiv 1 \mod{K}\)
然后你可以构造出来一种走法使得每条边都出现 \(2\) 次
在 \(dfs\) 树上走,遇见非树边就上去再下来,剩下的树肯定能只走 \(2\) 遍
需要哪一条边多走就循环走几次
所以可以构造出来一种走法使得这个走法的贡献为 \(2\)
然后只需要判断 \((u,v)\) 之间是否存在长度为偶数的路径如果存在答案为 \(0\) 否则为 \(1\)
为 \(0\) 的走法就是在偶数路径上走,再上到那个走法里一直走,最后再从同一个地方下来走完最后的路径
为 \(1\) 的同理
至于判断是否存在长度为偶数的路径 可以用冰茶姬,把一个点拆成奇点和偶点
路径长度为奇数就连奇点和偶点,为偶数就奇点连奇点
然后看看 \((u,v)\) 的两个偶点或者奇点是不是在同一个冰茶姬里就行了
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,q;
int fa[100010],g[100010];
struct E{int x,y,z;}ee[100010];
int gcd(int x,int y){return (!y)?x:gcd(y,x%y);}
int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
inline void merge(int x,int y){
x=getfa(fa[x]),y=getfa(fa[y]);
if(x!=y) fa[x]=y,g[y]=gcd(g[y],g[x]);
}
namespace DSU{
int fa[200010];//i i+n 0 1
int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
inline void init(){for(int i=1;i<=n*2;i++) fa[i]=i;}
inline void merge(int x,int y){x=getfa(fa[x]);y=getfa(fa[y]);if(x!=y) fa[x]=y;}
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
n=read(),m=read(),q=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
ee[i].x=read(),ee[i].y=read(),ee[i].z=read();
merge(ee[i].x,ee[i].y);
g[getfa(ee[i].x)]=gcd(ee[i].z,g[getfa(ee[i].x)]);
}
for(int i=1;i<=m;i++) ee[i].z/=g[getfa(ee[i].x)];
DSU::init();
for(int i=1,x,y,z;i<=m;i++){
x=ee[i].x,y=ee[i].y,z=ee[i].z;
if(z&1){
DSU::merge(x,y+n);
DSU::merge(x+n,y);
}else{
DSU::merge(x,y);
DSU::merge(x+n,y+n);
}
}
for(int i=1,x,y,k,gggg;i<=q;i++){
x=read(),y=read(),k=read();
if(getfa(x)!=getfa(y)){puts("NIE");continue;}
else if(k&1){puts("0");continue;}
else{
gggg=gcd(k,g[getfa(x)]);
if(DSU::getfa(x)==DSU::getfa(y)) printf("0\n");
else printf("%lld\n",gggg);
}
}
return 0;
}
C. Hack
容易发现在同一个 \(SCC\) 里的边一定不能被选择,所以先缩点
然后对于 \(SCC\) 之间的桥则可以被选择
如果没有只能经过一条的限制就可以直接跑个最小割
那么考虑有限制,拿样例举例子
发现最小割割掉的是上面的 \(1\) 和下面的 \(1\) ,但这样会有一条路径经过 \(2\) 条选择的边
不想让这个情况发生 于是把中间的那一条边的反向边的流量改成 \(\infty\)
这样就存在了一条从源点到汇点的路了,于是这种情况就不能成为最小割了
那么对于每一条边都把反向边的流量改成 \(\infty\) 就可以避免经过两条边了
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,S,T,ans;
int q[110],h,t,dis[110];
int head[110],HD[110],ver[5010],edge[5010],to[5010],tot=1;
int dfn[110],low[110],id[110],col,clo,stk[110],p;
struct E{int x,y,k;}ee[2510];
vector<int> g[110];
bool vis[110];
inline void add(int x,int y,int z){
ver[++tot]=y,edge[tot]=z,to[tot]=head[x],head[x]=tot;
ver[++tot]=x,edge[tot]=inf,to[tot]=head[y],head[y]=tot;
}
inline bool bfs(){
for(int i=1;i<=col;i++) dis[i]=inf;
dis[S]=0,q[h=t=1]=S;memcpy(head,HD,sizeof(head));
while(h<=t){
int x=q[h++];
for(int i=head[x];i;i=to[i]) if(edge[i]){
int y=ver[i];
if(dis[y]>dis[x]+1) dis[y]=dis[x]+1,q[++t]=y;
}
if(x==T) return true;
}
return false;
}
int dfs(int x,int in){
if(x==T) return in;
int res=in,go;
for(int i=head[x];i;head[x]=i=to[i]) if(edge[i]){
int y=ver[i];
if(dis[y]==dis[x]+1){
go=dfs(y,min(edge[i],res));
if(go) res-=go,edge[i]-=go,edge[i^1]+=go;
else dis[y]=0;
}
if(!res) break;
}
return in-res;
}
void tarjan(int x){
dfn[x]=low[x]=++clo;stk[++p]=x;vis[x]=1;
for(auto y:g[x]){
if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
else if(vis[y]) low[x]=min(low[x],dfn[y]);
}
int k;
if(dfn[x]==low[x]){
col++;
do{k=stk[p--];id[k]=col;vis[k]=0;}while(k!=x);
}
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
n=read(),m=read();
for(int i=1;i<=m;i++){
ee[i].x=read()+1,ee[i].y=read()+1,ee[i].k=read();
g[ee[i].x].emplace_back(ee[i].y);
}
tarjan(1);if(id[1]==id[n]) puts("-1"),exit(0);
for(int i=1,x,y;i<=m;i++){
x=ee[i].x,y=ee[i].y;
if(id[x]==id[y]) continue;
add(id[x],id[y],ee[i].k);
}
memcpy(HD,head,sizeof(head));S=id[1],T=id[n];
while(bfs()) ans+=dfs(S,inf);
printf("%lld\n",ans);
return 0;
}