2024.10.04 刷题记录

2024.10.04 刷题记录

P6764 APIO2020 粉刷墙壁

不难发现题目大意是每次选一个点,向后覆盖一个长度为 m 的段。

那我们只关心从点 i 开始能不能向后覆盖长度大于等于 m 的距离。

f[i][j] 表示第 i 段墙,被第 j 个承包商刷的最大可以向后覆盖的段数(连续,可以刷多次,每次起点在上一次的区间内)。

转移:

f[i][j]=f[i+1][(j+1)modm]

其中第 j 个承包商喜欢第 ci 个颜色。

由于喜欢一个颜色的承包商小于 f(k)200 个,复杂度 O(nf(k))

需要进行滚动数组优化空间。

算要求数,可以转化为拼接线段的模型,每次选出一个起点之后,前一个起点一定在 [iM+1,i) 这个区间内,暴力跑出最远位置即可。

// #include"paint.h"
#include<bits/stdc++.h>
using namespace std;

const int maxn=4e5+3;

int col[maxn],p[maxn],dp[2][maxn];

vector<int> s[maxn];
int minimumInstructions(int n,int m,int k,vector<int> c,vector<int> a,vector<vector<int>> b)
{
    int ans=0;
    for(int i=1;i<=n;++i) col[i]=c[i-1]+1;
    for(int i=1;i<=m;++i) for(int j:b[i-1]) s[j+1].push_back(i);
    for(int x,i=n;x=i&1,i;--i)
    {
        for(int j:s[col[i]]) p[i]|=(dp[x][j]=dp[!x][j%m+1]+1)>=m;
        for(int j:s[col[i+1]]) dp[!x][j]=0;
    }
    for(int i=n-m+1;i;--i)
    {
        if(++ans,!p[i]) return -1;
        for(int j=max(i-m,1);j<i;++j)
        {
            if(p[j])
            {
                i=j+1;
                break;
            }
        }
    }
    return ans;
}
// int main()
// {
//     int n,m,k;
//     scanf("%d%d%d",&n,&m,&k);
//     vector<int>c(n),a(m);
//     vector<vector<int>>b(m);
//     for(int i=0;i<n;++i) scanf("%d",&c[i]);
//     for(int i=0;i<m;++i)
//     {
//         scanf("%d",&a[i]);b[i].resize(a[i]);
//         for(int j=0;j<a[i];j++) scanf("%d",&b[i][j]);
//     }
//     printf("%d",minimumInstructions(n,m,k,c,a,b));
//     return 0;
// }

P7929 COCI2021-2022#1 Logičari

基环树 dp,从更方便的角度思考,看做树和多出来的一条边,要注意一个被染色的点周围也要有一个点被染色。

考虑树证明处理,设 dp[i][0/1][0/1] 表示第 i 个点是否染色,i 是否有一个儿子染色。

有转移:

dp[u][0][0]=vu.sonsdp[v][0][1]dp[u][0][1]=vu.sonsdp[v][0][1]+min(dp[v][1][1]dp[v][0][1])dp[u][1][0]=1+vu.sonsdp[v][0][0]dp[u][0][0]=vu.sonsdp[v][0][1]+min(dp[v][1][0]dp[v][0][0])

对于叶子节点:dp[u][0][0]=0,dp[u][0][1]=inf,dp[u][1][0]=1,dp[u][1][1]=inf

接着分类讨论一下通过多出来的边连接的两个点,其中其中一个为根(rt)(确实也要从 rt 开始 dp),一个为根的兄弟(rtbro)。

下面的 1 表示被染色,0 表示未被染色。

  1. rt=1,rtbro=0

    dp[rtbro][1][1]=inf,dp[rtbro][1][0]=inf,dp[rtbro][0][1]=dp[rtbro][0][0],dp[rtbro][0][0]=inf

    其中 dp[rtbro][0][1]=dp[rtbro][0][0] 表示 dp[rtbro][0][1] 需要通过 dp[rtbro][0][0] 的方程转移,下同。

    答案为 dp[rt][1][1]

  2. rt=1,rtbro=1

    dp[rtbro][1][1]=dp[rtbro][1][0],dp[rtbro][1][0]=inf,dp[rtbro][0][1]=inf,dp[rtbro][0][0]=inf

    答案为 dp[rt][1][0]

  3. rt=0,rtbro=0

    dp[rtbro][1][1]=inf,dp[rtbro][1][0]=inf

    答案为 dp[rt][0][1]

  4. rt=0,rtbro=1

    dp[rtbro][0][1]=inf,dp[rtbro][0][0]=inf

    答案为 dp[rt][0][0]

若上述四种情况均为 inf,则无解。

否则取最小值即可。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

#define inf 1e9

const int maxn=1e5+5;

struct Edge
{
    int tot;
    int head[maxn];
    struct edgenode{int to,nxt;}edge[maxn*2];
    inline void add(int x,int y)
    {
        tot++;
        edge[tot].to=y;
        edge[tot].nxt=head[x];
        head[x]=tot;
    }
}T;

int n,rt,rt_bro,flg;
int f[maxn];

ll ans=inf;
ll dp[maxn][2][2];

inline int fr(int u){return f[u]==u?u:f[u]=fr(f[u]);}

inline void dfs(int u,int f)
{
    dp[u][0][1]=dp[u][1][1]=inf;
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(v==f) continue;
        dfs(v,u);
        dp[u][0][0]+=dp[v][0][1];
        if(dp[v][1][1]<inf) dp[u][0][1]=min(dp[u][0][1],dp[v][1][1]-dp[v][0][1]);
        dp[u][1][0]+=dp[v][0][0];
        if(dp[v][1][0]<inf) dp[u][1][1]=min(dp[u][1][1],dp[v][1][0]-dp[v][0][0]);
    }
    dp[u][0][1]=dp[u][0][0]+dp[u][0][1];
    dp[u][1][0]++;
    dp[u][1][1]=dp[u][1][0]+dp[u][1][1];
    if(u==rt_bro)
    {
        if(flg==1)
        {
            dp[u][1][1]=inf,dp[u][1][0]=inf,dp[u][0][1]=dp[u][0][0],dp[u][0][0]=inf;
        }
        else if(flg==2)
        {
            dp[u][1][1]=dp[u][1][0],dp[u][1][0]=inf,dp[u][0][1]=inf,dp[u][0][0]=inf;
        }
        else if(flg==3)
        {
            dp[u][1][0]=inf,dp[u][1][1]=inf;
        }
        else
        {
            dp[u][0][0]=inf,dp[u][0][1]=inf;
        }
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        int fu=fr(u),fv=fr(v);
        if(fu==fv) {rt=u,rt_bro=v;continue;}
        T.add(u,v),T.add(v,u);
        f[fu]=fv;
    }
    memset(dp,0,sizeof(dp));
    flg=1;
    dfs(rt,0);
    ans=min(ans,dp[rt][1][1]);
    memset(dp,0,sizeof(dp));
    flg=2;
    dfs(rt,0);
    ans=min(ans,dp[rt][1][0]);
    memset(dp,0,sizeof(dp));
    flg=3;
    dfs(rt,0);
    ans=min(ans,dp[rt][0][1]);
    memset(dp,0,sizeof(dp));
    flg=4;
    dfs(rt,0);
    ans=min(ans,dp[rt][0][0]);
    printf("%lld",ans<inf?ans:-1);
}

P10044 CCPC 2023 北京市赛 最小环

诈骗题。

先拓扑把出度或入度为 0 的点全部删除,然后对于剩下的点,如果出度入度就进行缩边,边权等于原来两条边的边权和。

剩下的点至少是三度点,而一条边只能提供两度,满足 3n2m

结合 mn1500,解得 n4000,m4500

这里的 m,n 都是新图中的,所以只需要建出新图每个点跑 dij,然后枚举边成环即可。

需要特殊处理一下度为 2 的点成环的情况。

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define int long long
#define pli pair<ll,int>
#define fi first
#define se second

const int maxn=3e5+5;

inline int read()
{
    int x;
    x=0;bool flag(0);char ch=getchar();
    while(!isdigit(ch)) flag=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    flag?x=-x:0;
    return x;
}

struct Edge
{
    int tot;
    int head[maxn];
    struct edgenode{int to,nxt;ll w;}edge[maxn*2];
    inline void add(int x,int y,int z)
    {
        tot++;
        edge[tot].to=y;
        edge[tot].w=z;
        edge[tot].nxt=head[x];
        head[x]=tot;
    }
}G,fG;

int n,m;
int from[maxn],rd[maxn],cd[maxn];

bool vis[maxn],del[maxn];

ll ans=1e18;
ll dis[maxn];

inline void dij(int st)
{
    for(int i=0;i<=n;i++) vis[i]=false,dis[i]=1e18;
    dis[st]=0;
    priority_queue<pli,vector<pli>,greater<pli>>que;
    que.push({0,st});
    while(!que.empty())
    {
        int u=que.top().se;que.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=G.head[u];i;i=G.edge[i].nxt)
        {
            int v=G.edge[i].to;
            if(dis[v]>dis[u]+G.edge[i].w)
            {
                dis[v]=dis[u]+G.edge[i].w;
                que.push({dis[v],v});
            }
        }
    }
}
inline void bfs()
{
    queue<int>que;
    for(int i=1;i<=n;i++) if(!rd[i]||!cd[i]) que.push(i),vis[i]=true;
    while(!que.empty())
    {
        int u=que.front();que.pop();
        if(!rd[u])
            for(int i=G.head[u];i;i=G.edge[i].nxt)
            {
                int v=G.edge[i].to;
                rd[v]--;
                if(!vis[v]&&!rd[v]) que.push(v),vis[v]=true;
            }
        else
            for(int i=fG.head[u];i;i=fG.edge[i].nxt)
            {
                int v=fG.edge[i].to;
                cd[v]--;
                if(!vis[v]&&!cd[v]) que.push(v),vis[v]=true;
            }
    }
}

signed main()
{
    // scanf("%d%d",&n,&m);
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        // scanf("%d%d%d",&u,&v,&w);
        u=read(),v=read(),w=read();
        if(u==v) {ans=min(ans,(ll)w);continue;}
        cd[u]++,rd[v]++;
        G.add(u,v,w);
        fG.add(v,u,G.tot);
    }
    bfs();
    for(int i=1;i<=n;i++)
    {
        if(rd[i]==1&&cd[i]==1)
        {
            for(int j=fG.head[i];j;j=fG.edge[j].nxt)
            {
                int v=fG.edge[j].to;
                if(!vis[v]){from[i]=fG.edge[j].w;break;}
            }
            for(int j=G.head[i];j;j=G.edge[j].nxt)
            {
                int v=G.edge[j].to;
                if(!vis[v]){G.head[i]=j;break;}
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(cd[i]==1&&rd[i]==1&&G.edge[G.head[i]].to!=i)
        {
            G.edge[from[i]].to=G.edge[G.head[i]].to;
            G.edge[from[i]].w+=G.edge[G.head[i]].w;
            from[G.edge[G.head[i]].to]=from[i];
            del[i]=true;
        }
    }
    for(int u=1;u<=n;u++) for(int j=G.head[u];j;j=G.edge[j].nxt)
    {
        int v=G.edge[j].to;
        if(v==u) ans=min(ans,G.edge[j].w);
    }
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(rd[i]&&cd[i]&&!del[i])
        {
            cnt++;
            assert(cnt<=4000);
            dij(i);
            for(int u=1;u<=n;u++) if(dis[u]^dis[0]) for(int j=G.head[u];j;j=G.edge[j].nxt)
            {
                int v=G.edge[j].to;
                if(v==i) ans=min(ans,dis[u]+G.edge[j].w);
            }
        }
    }
    printf("%lld",ans==1e18?-1:ans);
}

P6604 HNOI2016 序列 加强版

P3246 HNOI2016 序列

转图论的笛卡尔树做法。

fi 为以 i 为右端点,任意左端点的贡献和。

p 为满足 ap<ai,q<i,的最大的 p

这里的 p 极其类似笛卡尔树中第一个向左走的父亲,这里可以用类笛卡尔树的方式求出 p(当然其他更简单的方法也可以)。

fi=fp+(ip)×ai

考虑一个查询,找到查询区间中最小的点 p,有贡献 (pl+1)×(rp+1)×ap

对于区间 (p,r] 每个点的贡献都是 fifp,因为 fi 中区间 [j,i](1jp) 的贡献来源于 fp

对于 [l,p) 序列反过来求过 g 即可。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

const int maxn=1e5+5;

namespace gen{
	typedef unsigned long long ull;
	ull s,a,b,c,lastans=0;
	ull rand(){
		return s^=(a+b*lastans)%c;
	}
};

int n,m,tp;
int a[maxn],st[maxn][25],pre[maxn],nxt[maxn];

ll f[maxn],sf[maxn],g[maxn],sg[maxn];

inline int cmp(int x,int y){return a[x]<a[y]?x:y;}
inline int qry(int l,int r)
{
    int k=__lg(r-l+1);
    return cmp(st[l][k],st[r-(1<<k)+1][k]);
}

int main()
{
    a[0]=2e9;
    scanf("%d%d%d",&n,&m,&tp);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),st[i][0]=i;
    for(int i=1;i<=20;i++) for(int j=1;j+(1<<i)-1<=n;j++)
        st[j][i]=cmp(st[j][i-1],st[j+(1<<(i-1))][i-1]);
    stack<int>stk;
    for(int i=1;i<=n;i++)
    {
        while(!stk.empty())
        {
            if(a[stk.top()]>=a[i]) nxt[stk.top()]=i,stk.pop();
            else break;
        }
        if(!stk.empty()) pre[i]=stk.top();
        stk.push(i);
    }
    for(int i=1;i<=n;i++) f[i]=f[pre[i]]+1ll*a[i]*(i-pre[i]),sf[i]=sf[i-1]+f[i];
    for(int i=n;i;i--) g[i]=g[nxt[i]]+1ll*a[i]*(nxt[i]-i),sg[i]=sg[i+1]+g[i];
    if(tp) cin>>gen::s>>gen::a>>gen::b>>gen::c;
    unsigned ll res=0;
    for(int i=1,l,r;i<=m;i++)
    {
        if(tp==0) scanf("%d%d",&l,&r);
        else {
			l=gen::rand()%n+1;
			r=gen::rand()%n+1;
			if(l>r) swap(l,r);
		}
        ll p=qry(l,r),ans=0;
        ans=a[p]*(r-p+1)*(p-l+1);
        ans+=sf[r]-sf[p]-f[p]*(r-p);
        ans+=sg[l]-sg[p]-g[p]*(p-l);
        res^=ans;
        gen::lastans=ans;
    }
    printf("%lld",res);
}

P6628 省选联考 2020 B 卷 丁香之路

必经 m 条边,和 s,i 两个点。

这些边和点放置于图中,此时若只有一个连通块,最优方案肯定希望 m 条边与 i,s 构成欧拉图,其中起点为 s,终点为 i

遂发现若 m 条边不构成欧拉图,加边肯定比走重复的边更合适,结合题目手玩几组即可发现。

于是如下构造,先连一条权为 0 的边联通 s,i,然后从 1 开始遍历,若 p 点的度数为奇数,则向 p+1 连边,最后连出的图肯定是最小的,而且构成欧拉回路。

再考虑构造后分成了多个连通块,在连通块间求最小生成树,树边重复2次就是不同连通块的贡献(三角形的边长关系可以粗略证明)。

最后答案就是2倍树边加构造的边加最初 m 条边。

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define pipii pair<int,pair<int,int>>
#define fi first
#define se second

const int maxn=2505;

int n,m,s;
int bel[maxn],f[maxn],deg[maxn];

ll sum;

inline int fr(int u){return f[u]==u?u:f[u]=fr(f[u]);}

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        sum+=abs(u-v);deg[u]++,deg[v]++;
        int fu=fr(u),fv=fr(v);
        if(fu!=fv) f[fu]=fv;
    }
    for(int i=1;i<=n;i++) bel[i]=fr(i);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++) f[j]=j;
        deg[s]++,deg[i]++;
        f[fr(bel[s])]=fr(bel[i]);
        ll ans=sum;int pre=0;
        for(int j=1;j<=n;j++)
        {
            if(deg[j]&1)
            {
                if(pre)
                {
                    ans+=j-pre;
                    for(int k=pre;k<j;k++) f[fr(bel[k])]=fr(bel[j]);
                    pre=0;
                }
                else pre=j;
            }
        }
        vector<pipii>vec;
        for(int j=1;j<=n;j++)
        {
            if(deg[j])
            {
                if(pre&&fr(bel[j])!=fr(bel[pre])) vec.push_back({j-pre,{bel[j],bel[pre]}});
                pre=j;
            }
        }
        sort(vec.begin(),vec.end());
        for(auto j:vec)
        {
            int u=j.se.fi,v=j.se.se;
            int fu=fr(u),fv=fr(v);
            if(fu!=fv) f[fu]=fv,ans+=j.fi*2;
        }
        deg[s]--,deg[i]--;
        printf("%lld ",ans);
    }
}
posted @   彬彬冰激凌  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示