冲刺国赛模拟 19

感觉越来越菜了。实在是懒得思考。不知道为啥。

现在有动力学数学了,但是是纯数。还是没动力学 oi。

矩阵

签到题。正解二维分块,\(O(\sqrt{nm})\) 修改 \(O(1)\) 查询。事实上二维树状数组可以艹过去。我是对每一行开树状数组的写法。这题有不少奇异写法来着。

见证了本机 2.1s tlecoders 0.45s 的奇异现象。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cstring>
#include <ctime>
using namespace std;
int n,m,q;
struct BIT{
    int c[1010];
    #define lowbit(x) (x&-x)
    void update(int x,int val){
        while(x<=m)c[x]+=val,x+=lowbit(x);
    }
    int query(int x){
        int sum=0;
        while(x)sum+=c[x],x-=lowbit(x);
        return sum;
    }
}c[1010];
int query(int x,int l,int r){
    return c[x].query(r)-c[x].query(l-1);
}
int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
    return x*f;
}
signed main(){
    n=read();m=read();q=read();
    for(int i=1;i<=q;i++){
        int od=read(),x=read(),y=read();
        if(od==1){
            int z=read();
            for(int j=y;j<=n;j++)c[j].update(x,z);
        }
        else{
            int a=read(),b=read();
            if(x>a)swap(x,a);
            if(y>b)swap(y,b);
            int ans=query(b,x,a)-query(y-1,x,a);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

种草

为啥没想到费用流。

首先对于 \(a\) 全都相等有个套路的费用流模型:顺序往下一个点连 \(a\) 容量边,然后每个方案从 \(l\to r+1\) 连容量 \(1\) 代价 \(w\) 的边,跑最大费用最大流。

现在 \(a\) 没限制了,于是我们就是有了若干 \(S\) 往后连的必选的方案。实际上直接连就行了,因为最大费用一定会选上。

这是 \(10^5\) 个点边的流,但是边权只有 \(20\),因此最多跑 \(20\) 次最短路。

出题人不做人,卡 spfa,要写原始对偶。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <cstring>
#define int long long
using namespace std;
int n,m,S,T,ans,cost,a[100010],h[100010];
struct node{
    int u,v,w,val,next;
    int getval(){
        return h[u]-h[v]+val;
    }
}edge[1000010];
int t=1,head[100010];
void Add(int u,int v,int w,int val){
    edge[++t].v=v;edge[t].u=u;edge[t].w=w;edge[t].val=val;edge[t].next=head[u];head[u]=t;
}
void add(int u,int v,int w,int val){
    Add(u,v,w,val);Add(v,u,0,-val);
}
int dis[100010],pre[100010],flow[100010];
bool vis[100010];
struct stu{
    int x,w;
    bool operator<(const stu&s)const{
        return w>s.w;
    }
};
priority_queue<stu>q;
bool dijkstra(int s,int t){
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    memset(flow,0,sizeof(flow));
    q.push({s,0});
    dis[s]=0;flow[s]=0x3f3f3f3f;
    while(!q.empty()){
        int x=q.top().x;q.pop();
        if(!vis[x]){
            vis[x]=true;
            for(int i=head[x];i;i=edge[i].next){
                if(edge[i].w&&dis[edge[i].v]>dis[x]+edge[i].getval()){
                    dis[edge[i].v]=dis[x]+edge[i].getval();
                    flow[edge[i].v]=min(flow[x],edge[i].w);
                    pre[edge[i].v]=i;
                    q.push({edge[i].v,dis[edge[i].v]});
                }
            }
        }
    }
    if(flow[t]==0)return false;
    for(int x=t;x!=s;x=edge[pre[x]].u)edge[pre[x]].w-=flow[t],edge[pre[x]^1].w+=flow[t];
    return true;
}
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);T=n+1;S=n+2;
    for(int i=1;i<=n;i++)add(i,i+1,a[i],0);
    for(int i=1;i<=n;i++)if(a[i]!=a[i-1])add(S,i,a[i]-a[i-1],0);
    for(int i=1;i<=m;i++){
        int l,r,w;scanf("%lld%lld%lld",&l,&r,&w);
        add(l,r+1,1,-w);
    }
    memset(h,0x3f,sizeof(h));
    h[S]=0;
    for(int i=head[S];i;i=edge[i].next){
        if(edge[i].w)h[edge[i].v]=min(h[edge[i].v],0ll);
    }
    for(int x=1;x<=n;x++){
        for(int i=head[x];i;i=edge[i].next){
            if(edge[i].w)h[edge[i].v]=min(h[edge[i].v],h[x]+edge[i].val);
        }
    }
    if(h[0]==h[T]){
        puts("0");return 0;
    }
    while(dijkstra(S,T)){
        for(int i=1;i<=n+2;i++)h[i]+=dis[i];
        ans+=flow[T];cost+=flow[T]*h[T];
    }
    printf("%lld\n",-cost);
    return 0;
}

基环树

绪山真寻。现在内心真的是个爷们吗。不好说。

树的情况可以设 \(dp_{0,1,2}\) 为剩下几条边的一节的价值和,合并的转移是个背包。然而直接做容量是 \(O(n)\) 的过不了,我们按照 https://www.luogu.com.cn/blog/EntropyIncreaser/sui-ji-bian-liang-qian-zhui-hu-di-kong-zhi 的结论,随机化之后只有 \(O(\sqrt n)\) 的容量,就能直接做了。

基环树上就对环边大力讨论组成路径上哪一条边或者不用就好了。(dfs 四遍)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cstring>
#include <ctime>
#include <random>
#define int long long
using namespace std;
mt19937 rnd(time(0)^(unsigned long long)(new char));
int n,ans,dp[100010][3],f[100010],h[100010];
vector<pair<int,int> >g[100010];
bool vis[100010];
int S,T,val;
void dfs1(int x,int f){
	vis[x]=true;
	for(pair<int,int> p:g[x]){
		int v=p.first,w=p.second;
		if(v==f)continue;
		if(vis[v]){
			S=x,T=v,val=w;
		}
		else dfs1(v,x);
	}
	vis[x]=false;
}
void dfs(int x,int fa,int tp){
	for(pair<int,int> p:g[x]){
		int v=p.first,w=p.second;
		if(v==fa)continue;
		if(v==S&&x==T)continue;
		if(v==T&&x==S)continue;
		dfs(v,x,tp);
	}
	int size=(int)sqrt(g[x].size())+1<<2;
	int len=size>>1;
	for(int i=0;i<=size;i++)f[i]=h[i]=-0x3f3f3f3f;
	f[len]=0;
	for(pair<int,int> p:g[x]){
		int v=p.first,w=p.second;
		if(v==fa)continue;
		if(v==S&&x==T)continue;
		if(v==T&&x==S)continue;
		for(int i=0;i<=size;i++)h[i]=f[i]+max(dp[v][0],dp[v][2]+w);
		for(int i=0;i<size;i++)h[i+1]=max(h[i+1],f[i]+dp[v][0]+w);
		for(int i=1;i<=size;i++)h[i-1]=max(h[i-1],f[i]+dp[v][1]+w);
		for(int i=0;i<=size;i++)f[i]=h[i];
	}
	if(x==T){
		if(tp==0)for(int i=0;i<=size;i++)h[i+1]=f[i]+val;
		if(tp==1)for(int i=1;i<=size;i++)h[i-1]=f[i]+val;
		if(tp==2)for(int i=0;i<=size;i++)h[i]=f[i]+val;
		for(int i=0;i<=size;i++)f[i]=h[i];
	}
	dp[x][0]=f[len];
	dp[x][1]=f[len+1];
	dp[x][2]=f[len-1];
}
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		int u,v,w;scanf("%lld%lld%lld",&u,&v,&w);
		g[u].push_back(make_pair(v,w));g[v].push_back(make_pair(u,w));
	}
	for(int i=1;i<=n;i++)shuffle(g[i].begin(),g[i].end(),rnd);
	dfs1(1,0);
	dfs(S,0,0);
	ans=max(ans,dp[S][0]);
	dfs(S,0,1);
	ans=max(ans,dp[S][1]);
	dfs(S,0,2);
	ans=max(ans,dp[S][2]);
	dfs(S,0,3);
	ans=max(ans,dp[S][0]);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2023-06-15 19:50  gtm1514  阅读(17)  评论(0编辑  收藏  举报