Loading

杂题选做

[JOI 2021 Final] ロボット

一个直观的做法,对于每条边\((x,y,c,w)\),如果改颜色只出现一次,连边 \((x,y,0)\),否则有两种策略,第一种是把该颜色直接变,连边 \((x,y,w)\),第二种是改变该颜色的其他边,如果和为 \(sum\),连边 \((x,y,sum-w)\)
第二种连边的正确在于:如果被改变的边后续被再次访问到,因为 \(sum-w\leq w\),所以可以直接用第一种策略从这条被再次访问过的边,一定不劣。
但是该做法漏了一种情况,如果我们通过策略一改变了一条原本为 \(c\) 的边进入 \(x\),然后通过策略二从一条为 \(c\) 的边出去,那么进入的边就不需要再次被算入贡献了。

因此对于每个点 \(x\),每个颜色 \(c\),都建立一个虚点 \(o\),对于一条边 \((x,y,c,w)\),连边 \((o,y,sum-w)\),连边 \((y,o,0)\),代表上述情况。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
typedef long long LL;
int n,m;
int U[N],V[N],C[N],W[N];
struct edge
{
    int y,next;
    LL v;
}e[2*N];
int flink[N],t=0;
void add(int x,int y,LL v)
{
    e[++t].y=y;
    e[t].v=v;
    e[t].next=flink[x];
    flink[x]=t;
}
LL dis[N];
bool vis[N];
#define PII pair<LL,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
int tot;
priority_queue<PII> q;
void dijs()
{
    for(int i=1;i<=tot;i++)dis[i]=1e18;
    dis[1]=0;q.push(mk(0,1));
    while(!q.empty())
    {
        int x=q.top().second;
        q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=flink[x];i;i=e[i].next)
        {
            int y=e[i].y;
            if(dis[y]>dis[x]+e[i].v)
            {
                dis[y]=dis[x]+e[i].v;
                q.push(mk(-dis[y],y));
            }
        }
    }
}
map<int,vector<int> > mp[N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d %d",&U[i],&V[i],&C[i],&W[i]);
        mp[U[i]][C[i]].push_back(i);
        mp[V[i]][C[i]].push_back(i);
    }
    tot=n;
    for(int i=1;i<=n;i++)
    {
        for(auto c:mp[i])
        {
            int x=++tot;
            add(i,x,0);
            LL sum=0;
            for(int j:c.second)sum+=W[j];
            for(int j:c.second)
            {
                int y=U[j]+V[j]-i;
                if(c.second.size()==1)
                {
                    add(i,y,0);
                    break;
                }
                add(x,y,sum-W[j]);
                add(y,x,0);
                add(i,y,W[j]);
            }
        }
    }
    dijs();
    if(dis[n]==1e18)cout<<-1;
    else cout<<dis[n];
    return 0;
}

[JOI Open 2022] 放学路(School Road)

先考虑原图是一个点双的情况,考虑用类似广义串并联图的方式处理:
对于一度点,直接删掉即可。
对于二度点,直接把两条边合起来。
对于重边,如果边权不相等,直接输出 \(1\),否则保留一条即可。
那么最后剩下的图,如果只剩下 \(1\to n\),显然是 \(0\),否则也可以证明是 \(1\)
对于不是点双的情况,和上述做法的区别在于,我们只保留存在 \(1\to x\to n\) 的简单路径的点 \(x\)
加入一条边 \((1,n,dis(1,n)\),显然不影响答案,同时 \(1,n\) 此时一定在一个点双内,可以发现此时可能的 \(x\) 也一定在该点双内,因此保留该点双,套用上面的做法就好了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
#define int long long
typedef long long LL;

struct edge
{
    int y,next,v;
}e[2*N];
int link[N],t=0;
void add(int x,int y,int v)
{
    e[++t].y=y;
    e[t].v=v;
    e[t].next=link[x];
    link[x]=t;
}
int n,m;
int low[N],dfn[N],num;
stack<int> st;
int seq[N],C=0;
bool ins[N];
void manage()
{
    bool A=0,B=0;
    for(int i=1;i<=C;i++)
    {
        A|=(seq[i]==1);
        B|=(seq[i]==n);
    }
    if(A&&B)
    {
        for(int i=1;i<=C;i++)
        ins[seq[i]]=1;
    }
}
void tarjan(int x)
{
    low[x]=dfn[x]=++num;
    st.push(x);
    for(int i=link[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(!dfn[y])
        {
            tarjan(y);
            if(low[y]>=dfn[x])
            {
                int z;
                C=0;
                do
                {
                    z=st.top();
                    st.pop();
                    seq[++C]=z;
                }while(z!=y);
                seq[++C]=x;
                manage();
            }
            low[x]=min(low[x],low[y]);
        }
        else low[x]=min(low[x],dfn[y]);
    }
}
int U[N],V[N],W[N];
LL dis[N];
bool vis[N];
#define PII pair<LL,int>
#define mk(x,y) make_pair(x,y)
priority_queue<PII> q;
void dijs()
{
    for(int i=1;i<=n;i++)dis[i]=1e18;
    dis[1]=0;q.push(mk(0,1));
    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;
            if(dis[y]>dis[x]+e[i].v)
            {
                dis[y]=dis[x]+e[i].v;
                q.push(mk(-dis[y],y));
            }
        }
    }
}
multiset<PII> G[N];
int deg[N];
queue<int> que;
void ers(int x,int y,int v)
{
    deg[x]--;deg[y]--;
    G[x].erase(G[x].find(mk(y,v)));
    G[y].erase(G[y].find(mk(x,v)));
}
int check(int x,int y)
{
    auto u=G[x].lower_bound(mk(y,0));
    if(u!=G[x].end()&&(u->first==y)) return u->second;
    return -1;
}
void apl(int x,int y,int v)
{
    int w=check(x,y);
    if(w!=-1&&w!=v)
    {
        printf("1");
        exit(0);
    }
    if(w!=-1)return;
    deg[x]++;deg[y]++;
    G[x].insert(mk(y,v));
    G[y].insert(mk(x,v));
}
void upd(int x);
void del(int x)
{
    if(!ins[x])return;
    auto u=(G[x].begin());
    int y=u->first,v=u->second;
    ers(x,y,v);
    ins[x]=0;
    upd(y);
}
void upd(int x)
{
    if(x!=1&&x!=n)
    {
        if(deg[x]==1)del(x);
        else if(deg[x]==2)que.push(x);
    }
}
signed main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        scanf("%lld %lld %lld",&U[i],&V[i],&W[i]);
        add(U[i],V[i],W[i]);
        add(V[i],U[i],W[i]);
    }
    dijs();
    ++m;
    U[m]=1;V[m]=n;W[m]=dis[n];
    add(1,n,dis[n]);add(n,1,dis[n]);
    tarjan(1);
    for(int i=1;i<=m;i++)
    {
        int x=U[i],y=V[i];
        if(ins[x]&&ins[y])apl(x,y,W[i]);
    }
    for(int i=1;i<=n;i++)upd(i);
    while(!que.empty())
    {
        int x=que.front();
        que.pop();
        if(!ins[x])continue;
        ins[x]=0;
        auto A=(G[x].begin()),B=(G[x].begin());B++;
        int ya=A->first,va=A->second;
        int yb=B->first,vb=B->second;
        ers(x,ya,va);ers(x,yb,vb);
        apl(ya,yb,va+vb);
        upd(ya);upd(yb);
    }
    if(G[1].size()==1&&check(1,n)!=-1)cout<<0;
    else cout<<1;
    return 0;
}

[UR #25]见贤思齐

首先考虑图是一棵树的情况,考虑对于每个点 \(x\),维护一棵以时间为下标的线段树,第 \(i\) 个位置的值 \(f_{x,i}\) 为第 \(i\) 天的增量(\(\in [0,1]\))。
考虑从 \(p_i\) 推向 \(i\) 时线段树的变化:

若某个时刻 \(t\)\(a_{t,p_x}=a_{t,x}\),且这个情况第一次发生,那么接下来我们会发现: \(f_{t+1,x}=1\),\(f_{t+k,x}=f_{t+k-1,p_x},k\geq 2\) ,也就是说,一定是把整个线段树平移了一个单位。
在发生这种情况之前,可以发现 \(f_{t,x}\) 要么一直是 \(0\) 要么一直是 \(1\) ,我们可以线段树二分找到上面的位置,然后就可以做了。
返回的时候需要撤回。
复杂度 \(O(n\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+7;
template<typename _T> inline void read(_T &x){
	x=0; char c=getchar(); bool f=0;
	for(; c<'0'||c>'9'; c=getchar()) f|=(c=='-');
	for(; c>='0'&&c<='9'; c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	x=(f)?(-x):x;
}
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 p[N],a[N];
int n,m;
int day[N];
vector<int> qry[N];
#define S 1,-n,2e5
bool vis[N];
bool ins[N];
int sum[N*4],cov[N*4],len[N*4];
struct info
{
	int k,sum,cov;
};
vector<info> seq;
void pushup(int k)
{
	seq.push_back((info){k,sum[k],cov[k]});
	sum[k]=sum[k<<1]+sum[k<<1|1];
}
void pushtag(int k,int v)
{
	seq.push_back((info){k,sum[k],cov[k]});
	sum[k]=v*len[k];
	cov[k]=v;
}
void pushdown(int k)
{
	if(cov[k]!=-1)
	{
		seq.push_back((info){k,sum[k],cov[k]});
		pushtag(k<<1,cov[k]);
		pushtag(k<<1|1,cov[k]);
		cov[k]=-1;
	}
}
const int Mx = 2e5;
void build(int k,int l,int r)
{
	len[k]=r-l+1;
	sum[k]=0;
	cov[k]=-1;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
int ask(int k,int l,int r,int L,int R)
{
	if(L<=l&&r<=R) return sum[k];
	pushdown(k);
	int mid=(l+r)>>1;
	int res=0;
	if(L<=mid)res+=ask(k<<1,l,mid,L,R);
	if(R>mid) res+=ask(k<<1|1,mid+1,r,L,R);
	return res;
}
void modify(int k,int l,int r,int L,int R,int v)
{
	if(L<=l&&r<=R)
	{
		pushtag(k,v);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)modify(k<<1,l,mid,L,R,v);
	if(R>mid) modify(k<<1|1,mid+1,r,L,R,v);
	pushup(k);
}
int det[N];
void Upd(int x,int l,int r,int v)
{
	if(l>r) return;
	l-=det[x];r-=det[x];
	modify(S,l,r,v);
}
int Sum(int x,int l,int r)
{
	if(l>r) return 0;
	l-=det[x];r-=det[x];
	return ask(S,l,r);
}
int D=0;
const int INF = -1e9;
int query1(int k,int l,int r)
{
	if(sum[k]-len[k]+D>0) 
	{
		D+=sum[k]-len[k];
		return INF;
	}
	if(l==r) return l;
	pushdown(k);
	int mid=(l+r)>>1;
	int p=query1(k<<1,l,mid);
	if(p!=INF) return p;
	return query1(k<<1|1,mid+1,r); 
}
int query2(int k,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)
	{
		int p=query1(k,l,r);
		return p;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	int p=INF;
	if(L<=mid)p=query2(k<<1,l,mid,L,R);
	if(p!=INF) return p;
	if(R>mid) p=query2(k<<1|1,mid+1,r,L,R);
	return p;
}
int query3(int k,int l,int r)
{
	if(sum[k]+D<0)
	{
		D+=sum[k];
		return INF;
	}
	if(l==r) return l;
	pushdown(k);
	int mid=(l+r)>>1;
	int p=query3(k<<1,l,mid);
	if(p!=INF) return p;
	return query3(k<<1|1,mid+1,r); 
}
int query4(int k,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)
	{
		int p=query3(k,l,r);
		return p;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	int p=INF;
	if(L<=mid)p=query4(k<<1,l,mid,L,R);
	if(p!=INF) return p;
	if(R>mid) p=query4(k<<1|1,mid+1,r,L,R);
	return p;
}
void put(int x)
{
	for(int i=0;i<=10;i++)
	printf("f(%d,%d)=%d\n",x,i,Sum(x,1,i));
}
void jump1(int x)
{
	//put(p[x]);
	D=a[p[x]]-a[x]+1;
	int ans=query2(S,-det[p[x]],2e5-det[p[x]]);
	if(ans==INF)ans=2e5;
	else ans+=det[p[x]];
	det[x]=det[p[x]]+1;
	Upd(x,1,ans,1);
	if(ans<2e5) Upd(x,ans+1,ans+1,1);
}
void jump2(int x)
{
	D=a[p[x]]-a[x];
	int ans=query4(S,-det[p[x]],2e5-det[p[x]]);
	if(ans==INF)ans=2e5;
	else ans+=det[p[x]];
	det[x]=det[p[x]]+1;
	Upd(x,1,ans,0);
	if(ans<2e5) Upd(x,ans+1,ans+1,1);
}
int ans[N];
void dfs(int x,int r)
{
	vis[x]=1;
	int lst=seq.size();
	if(x==r)
	{
		det[x]=0;
		Upd(x,1,2e5,1);
	}
	else 
	{
		if(a[p[x]]>=a[x]) jump1(x);
		else jump2(x);
	}
	//put(x);
	//cout<<endl;
	for(auto u:qry[x])ans[u]=a[x]+Sum(x,1,day[u]);
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y!=r)dfs(e[i].y,r);
	}
	//cout<<"clr"<<endl;
	while(seq.size()>lst)
	{
		int k=seq.back().k;
		sum[k]=seq.back().sum;
		cov[k]=seq.back().cov;
		seq.pop_back();
	}
}
int main()
{
	read(n);read(m);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)read(p[i]),add(p[i],i);
	for(int i=1;i<=m;i++)
	{
		int x,d;
		read(x);read(d);
		day[i]=d;
		qry[x].push_back(i);
	}
	build(S);
	for(int i=1;i<=n;i++)
	if(!vis[i]) 
	{
		int x=i;
		while(!vis[x])
		{
			vis[x]=1;
			x=p[x];
		}
		int mn=0;
		while(!ins[x])
		{
			ins[x]=1;
			if(!mn||a[x]<a[mn])mn=x; 
			x=p[x];
		}
		dfs(mn,mn);
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}
posted @ 2023-05-23 21:41  Larunatrecy  阅读(11)  评论(0)    收藏  举报