刷题记录 11 月合集

刷题记录 11 月合集

没啥时间记录了,趁着考 NOIP 前还有空赶紧记一下。

P1081 NOIP2012 提高组 开车旅行

先考虑暴力,每个点预处理出 in 中距离自己第一近和第二近的点(set 或平衡树找排名在 [rki2,rki+2] 中的点),然后对于每个 i 不断向后依次跳,直到结束,即可解决第二问;第一问类似的枚举出起始点也易于解决。

考虑优化,可以按照倍增的方法,设:

  • f[i][j][k] 为从城市 j 出发走 2i 天,k 先开车到达的城市。
  • fa[i][j][k] 为从城市 j 出发走 2i 天,k 先开车,A 开了多少路。
  • fb[i][j][k] 为从城市 j 出发走 2i 天,k 先开车,B 开了多少路。

类似倍增 Lca 的方法维护。

定义 calc(i,x) 为从城市 i 出发,走 x 距离所到达的城市和距离。

将暴力中的跳点换成倍增跳点,复杂度 O(mlogn)

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

#define int long long

#define inf 1e16

const int maxn=1e5+200;

int n,x0,m;
int h[maxn],s[maxn],x[maxn];

int f[25][maxn][2],da[25][maxn][2],db[25][maxn][2];

struct City
{
    int id,al;
    friend bool operator < (City a,City b){return a.al<b.al;}
};

int la,lb,ansid;
inline void calc(int S,int X)
{
    int p=S;
    la=0,lb=0;
    for(int i=18;~i;i--)
    {
        if(f[i][p][0]&&la+lb+da[i][p][0]+db[i][p][0]<=X)
        {
            la+=da[i][p][0];
            lb+=db[i][p][0];
            p=f[i][p][0];
        }
    }
}

inline void pre()
{
    multiset<City>s;
    h[0]=inf,h[n+1]=-inf;
    City st;
    st.id=0,st.al=inf;s.insert(st),s.insert(st);
    st.id=n+1,st.al=-inf;s.insert(st),s.insert(st);
    for(int i=n;i;i--)
    {
        int ga,gb;
        City now;
        now.id=i,now.al=h[i];
        s.insert(now);
        set<City>::iterator p=s.lower_bound(now);
        p--;
        int lt=(*p).id,lh=(*p).al;
        p++,p++;
        int ne=(*p).id,nh=(*p).al;
        p--;
        if(llabs(nh-h[i])>=llabs(h[i]-lh))
        {
            gb=lt;p--,p--;
            if(llabs(nh-h[i])>=llabs(h[i]-(*p).al)) ga=(*p).id;
            else ga=ne;
        }
        else
        {
            gb=ne;p++,p++;
            if(llabs(h[i]-(*p).al)>=llabs(lh-h[i])) ga=lt;
            else ga=(*p).id;
        }
        f[0][i][0]=ga,f[0][i][1]=gb;
        da[0][i][0]=llabs(h[ga]-h[i]);
        db[0][i][1]=llabs(h[gb]-h[i]);
    }
    // for(int i=1;i<=n;i++) cerr<<f[0][i][0]<<" "<<f[0][i][1]<<"\n";
    for(int i=1;i<=18;i++)
    {
        for(int j=1;j<=n;j++) for(int k=0;k<2;k++)
        {
            if(i==1)
            {
                f[1][j][k]=f[0][f[0][j][k]][1-k];
                da[1][j][k]=da[0][j][k]+da[0][f[0][j][k]][1-k];
                db[1][j][k]=db[0][j][k]+db[0][f[0][j][k]][1-k];
            }
            else
            {
                f[i][j][k]=f[i-1][f[i-1][j][k]][k];
                da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][k];
                db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][k];
            }
        }
    }
}

signed main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&h[i]);
    scanf("%lld%lld",&x0,&m);
    for(int i=1;i<=m;i++) scanf("%lld%lld",&s[i],&x[i]);
    pre();
    double ans=inf*1.0;
    for(int i=1;i<=n;i++)
    {
        calc(i,x0);
        double nowans=(double)la/(double)lb;
        if(nowans<ans)
        {
            ans=nowans;
            ansid=i;
        }
        else if(nowans==ans&&h[ansid]<h[i]) ansid=i;
    }
    printf("%lld\n",ansid);
    for(int i=1;i<=m;i++)
    {
        calc(s[i],x[i]);
        printf("%lld %lld\n",la,lb);
    }
}

本题有启发性,体现了从暴力到倍增优化的思想。

P9870 NOIP2023 双序列拓展

将问题进行转化,考虑成一个矩阵,其中第 i 行表示 fi ,第 j 列表示 gj

在矩阵上做双指针,满足:

  • fx<gy+1,向 (x,y+1) 走一步。
  • fx+1<gy,向 (x+1,y) 走一步。
  • fx+1<gy+1,向 (x+1,y+1) 走一步。

dp[x][y] 为是否可以从 (1,1) 通过上述转移走到 (x,y)

dp[i][j]=dp[i1][j]|dp[j][i1]|dp[i1][j1]

时间复杂度 O(Tnm)

考虑找到 f[i] 的最小值和 g[j] 的最大值,对于 f[i] 判断能不能从 f[i] 一直移动 y 走到 (i,m),如果不能那么注定无解。对于 g[j] 同理。

接下来 i 行和 j 列将问题分为了两个子问题,分别是左上角的矩形能否走到 i 行或者 j 列;右下角的矩形能否走到 n 行和 m 列。

按照类似的方法递归求解。

维护方面,维护出前缀最小值和后缀最大值可以简单的解决本题。

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

const int maxn=5e5+5;

struct node
{
    int mi,mx;
}sufX[maxn],sufY[maxn],preX[maxn],preY[maxn];

int c,n,m,q;
int tf[maxn],tg[maxn],ttf[maxn],ttg[maxn],g[maxn],f[maxn];

inline node updata(bool flg,node a,node b)
{
    if(a.mi==0||a.mx==0) return b;
    node res=a;
    if(!flg)
    {
        if(f[b.mi]<=f[res.mi]) res.mi=b.mi;
        if(f[b.mx]>=f[res.mx]) res.mx=b.mx;
    }
    else
    {
        if(g[b.mi]<=g[res.mi]) res.mi=b.mi;
        if(g[b.mx]>=g[res.mx]) res.mx=b.mx;
    }
    return res;
}

inline bool check1(int x,int y,int n,int m)
{
    if(x==1||y==1) return true;
    node X=preX[x-1],Y=preY[y-1];
    if(f[X.mi]<g[Y.mi]) return check1(X.mi,y,n,m);
    if(g[Y.mx]>f[X.mx]) return check1(x,Y.mx,n,m);
    return false;
}
inline bool check2(int x,int y,int n,int m)
{
    if(x==n||y==m) return true;
    node X=sufX[x+1],Y=sufY[y+1];
    if(f[X.mi]<g[Y.mi]) return check2(X.mi,y,n,m);
    if(g[Y.mx]>f[X.mx]) return check2(x,Y.mx,n,m);
    return false;
}
inline bool solve(int tmpf[],int tmpg[],int n,int m)
{
    memset(preX,0,sizeof(preX));
    memset(preY,0,sizeof(preY));
    memset(sufX,0,sizeof(sufX));
    memset(sufY,0,sizeof(sufY));
    if(tmpf[1]>=tmpg[1]) return false;
    for(int i=1;i<=n;i++) f[i]=tmpf[i];
    for(int i=1;i<=m;i++) g[i]=tmpg[i];
    for(int i=1;i<=n;i++) preX[i]=updata(0,preX[i-1],{i,i});
    for(int i=1;i<=m;i++) preY[i]=updata(1,preY[i-1],{i,i});
    for(int i=n;i>=1;i--) sufX[i]=updata(0,sufX[i+1],{i,i});
    for(int i=m;i>=1;i--) sufY[i]=updata(1,sufY[i+1],{i,i});
    node X=preX[n],Y=preY[m];
    if(f[X.mi]>=g[Y.mi]||g[Y.mx]<=f[X.mx]) return false;
    return check1(X.mi,Y.mx,n,m)&&check2(X.mi,Y.mx,n,m);
}

int main()
{
    // freopen("P9870_5.in","r",stdin);
    // freopen("P9870.out","w",stdout);
    scanf("%d%d%d%d",&c,&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%d",&tf[i]);
    for(int i=1;i<=m;i++) scanf("%d",&tg[i]);
    putchar((solve(tf,tg,n,m)||solve(tg,tf,m,n))?'1':'0');
    while(q--)
    {
        for(int i=1;i<=n;i++) ttf[i]=tf[i];
        for(int i=1;i<=m;i++) ttg[i]=tg[i];
        int kx,ky;
        scanf("%d%d",&kx,&ky);
        while(kx--){int x,y;scanf("%d%d",&x,&y);ttf[x]=y;}
        while(ky--){int x,y;scanf("%d%d",&x,&y);ttg[x]=y;}
        putchar((solve(ttf,ttg,n,m)||solve(ttg,ttf,m,n))?'1':'0');
    }
}

P9120 春季测试 2023 密码锁

随机化序列,枚举每一位移动的值,贪心的取。

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

const int maxn=5e4+5;

int n,k;
int a[maxn][10],mn[10],mx[10];

int main()
{
    srand(114514);
    int _;
    scanf("%d%d",&_,&k);
    while(_--)
    {
        scanf("%d",&n);
        for(int i=1;i<=k;i++) for(int j=1;j<=n;j++) scanf("%d",&a[j][i]);
        int tot=k==4?600:200;
        int ans=1e9;
        while(tot--)
        {
            random_shuffle(a+1,a+n+1);
            memset(mx,-0x3f,sizeof(mx));
            memset(mn,0x3f,sizeof(mn));
            for(int i=1;i<=n;i++)
            {
                int sum=1e9,p=0;
                for(int x=0;x<k;x++)
                {
                    int tmp=0;
                    for(int j=1;j<=k;j++)
                    {
                        int id=(j+x-1)%k+1;
                        tmp=max(tmp,max(mx[j],a[i][id])-min(mn[j],a[i][id]));
                    }
                    if(tmp<sum) sum=tmp,p=x;
                }
                for(int j=1;j<=k;j++)
                {
                    int id=(j+p-1)%k+1;
                    mx[j]=max(mx[j],a[i][id]);
                    mn[j]=min(mn[j],a[i][id]);
                }
                if(ans<sum) break;
            }
            int ret=0;
            for(int i=1;i<=k;i++)
                ret=max(ret,mx[i]-mn[i]);
            ans=min(ans,ret);
        }
        printf("%d\n",ans);
    }
}

P7962 NOIP2021 方差

不难发现一次操作的本质是交换相邻两项的差分,可以证明:
相邻三项 ai1,ai,ai+1 的差分为 x,aiai1,ai+1ai

令第二项为 ai1+ai+1ai,那么 ai1,ai1+ai+1ai,ai+1 的差分为 x,ai+1ai,aiai1

也就是可以任意交换两项,由于是求最小值,考虑退火。

每次随机交换两项,暴力此次的结果。

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

#define ll long long
#define int long long

const int maxn=1e4+5;

ll ans;

int n,l,r,len,mn;
int a[maxn],d[maxn],cf[maxn],tmp[maxn];

deque<int>que;

double down=0.97;

inline void fr()
{
    l=r=0;
    for(int i=1;i<n;i++)
        if(mn==cf[i]&&!l) l=r=i;
        else if(mn==cf[i]) r=i;
    len=r-l+1;
}
inline ll calc()
{
    ll now=0,sum=0;
    for(int i=1;i<n;i++) now+=cf[i],sum+=now;
    int aver=sum/n;
    now=0,sum=0;
    sum=aver*aver;
    for(int i=1;i<n;i++) now+=cf[i],sum+=(now-aver)*(now-aver);
    return sum/n;
}
inline void simulateAnneal()
{
    len=r-l+1;
    for(double T=2e5;T>1e-5;T*=down)
    {
        for(int i=1;i<n;i++){
            if(cf[i]==mn){
                l=i,r=l+len-1;
                break;
            }
        }
        for(int i=1;i<n;i++) tmp[i]=cf[i];
        int pos=rand()%(n-1-len)+1;
        if(pos>=l)
        {
            pos+=len;int t=cf[pos];
            for(int i=pos;i;i--)
            {
                if(cf[i-1]>t||i==1) {cf[i]=t;break;}
                cf[i]=cf[i-1];
            }
        }
        else
        {
            int t=cf[pos];
            for(int i=pos;i<n;i++)
            {
                if(cf[i+1]>t||i==n-1) {cf[i]=t;break;}
                cf[i]=cf[i+1];
            }
        }
        ll res=calc();
        ll dt=res-ans;
        if(dt<0) ans=res;
        else if(exp(-dt/T)*RAND_MAX<=rand())
            for(int i=1;i<n;i++) cf[i]=tmp[i];
    }
}

signed main()
{
    srand(time(0));
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]*=n;
    int st=clock();
    for(int i=2;i<=n;i++) d[i]=a[i]-a[i-1];
    sort(d+2,d+n+1);
    mn=d[2];
    for(int i=2;i<=n;i++)
    {
        if(i&1) que.push_back(d[i]);
        else que.push_front(d[i]);
    }
    for(int i=1;!que.empty();i++,que.pop_front()) cf[i]=que.front();
    fr();
    ans=calc();
    if(len==n-1) printf("%lld",ans),exit(0);
    while(clock()-st<950*1000) simulateAnneal();
    printf("%lld",ans);
}

P8866 NOIP2022 喵了个喵

题解

P1514 NOIP2010 提高组 引水入城

一开始证明部分想歪了。

第一行建满蓄水厂,枚举起点 bfs 判断所有蓄水厂能否覆盖第 n 行解决第一问。

第二问发现是每个蓄水厂可以覆盖第 n 行若干个点,考虑证明这些点连成了一段。

图中的蓝色点可以通过蓝色线覆盖两个黄色点,若第 n 行可以覆盖满,那么至少存在一条路径穿过蓝色线覆盖黄色点中间的点,那么蓝色线也可以用相同的路径覆盖中间的点。

所以每个蓄水厂覆盖了一段第 n 行中的点,那么剩下就是一个线段覆盖问题,贪心即可。

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

#define pii pair<int,int>
#define fi first
#define se second

const int maxn=505;

int n,m;
int a[maxn][maxn],fx[4][2]={{0,1},{0,-1},{1,0},{-1,0}};

pii t[maxn];

bool vis[maxn][maxn],cis[maxn];

inline bool pd(int x,int y)
{
    if(x<1||y<1||x>n||y>m) return 1;
    return 0;
}

inline void bfs(int x,int y)
{
    memset(vis,0,sizeof(vis));
    queue<pii>que;
    que.push({x,y});
    vis[x][y]=1;
    while(!que.empty())
    {
        int x=que.front().fi,y=que.front().se;
        que.pop();
        for(int i=0;i<4;i++)
        {
            int xx=x+fx[i][0],yy=y+fx[i][1];
            if(vis[xx][yy]||pd(xx,yy)) continue;
            if(a[xx][yy]<a[x][y])
            {
                vis[xx][yy]=1;
                que.push({xx,yy});
            }
        }
    }
    t[y].fi=1e9;
    for(int i=1;i<=m;i++)
    {
        cis[i]|=vis[n][i];
        if(vis[n][i]) t[y].fi=min(t[y].fi,i),t[y].se=max(t[y].se,i);
    }
}

int main()
{
    // freopen("P1514_1.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
    for(int i=1;i<=m;i++) bfs(1,i);
    int ans=0;
    for(int i=1;i<=m;i++) ans+=cis[i];
    if(ans<m) {printf("0\n%d",m-ans);return 0;}
    sort(t+1,t+m+1);
    int r=0,id=0,rmx=0;
    ans=0;
    for(int i=1;i<=m;i++)
    {
        while(t[id+1].fi<=i&&id+1<=m) id++,rmx=max(rmx,t[id].se);
        if(r<i) r=rmx,ans++;
    }
    printf("1\n%d",ans);
}

P1966 NOIP2013 提高组 火柴排队

拆式子。

(aibi)2=ai2+bi2+2aibi

转化为求 max(aibi)

ab 降序排序,第 i 位对应的 ai,bi 匹配是最大的,证明不难,留作读者自行思考。

如何求交换最少?

Aiai 对应的 bi 所在原来 b 中的位置,问题转化为将 A 排序最少交换多少次,交换本质是减少逆序对的过程,所以不管移动 a 序列还是 b 序列是没有差别的。

在降序排序过程中,将 a,b 原位置作为第二关键字排序可以有效减小逆序对个数。

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

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

const ll mod=1e8-3;
const int maxn=1e5+5;

struct treearray
{
    #define N 100000
    int ts[maxn];
    inline int lowbit(int x){return x&(-x);}
    inline void updata(int x,int y){for(;x<=N;x+=lowbit(x)) ts[x]+=y;}
    inline int getsum(int x){int sum=0;for(;x;x-=lowbit(x)) sum+=ts[x];return sum;}
}T;

ll ans;

int n;
int p[maxn];

pii a[maxn],b[maxn];

map<int,int>mp;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i].fi),a[i].se=i;
    for(int i=1;i<=n;i++) scanf("%d",&b[i].fi),b[i].se=i;
    sort(a+1,a+n+1),sort(b+1,b+n+1);
    for(int i=1;i<=n;i++) p[a[i].se]=b[i].se,mp[p[a[i].se]]=1;
    auto it1=mp.begin(),it2=it1;
    for(it2++;it2!=mp.end();it1++,it2++) it2->second+=it1->second;
    for(int i=1;i<=n;i++)
    {
        p[i]=mp[p[i]];
        ans+=T.getsum(n)-T.getsum(p[i]);
        T.updata(p[i],1);
        ans%=mod;
    }
    printf("%d",ans);
}

P1967 NOIP2013 提高组 货车运输

求树上两点间最小边权,倍增、树剖、Kruskal 重构树均可解决本题。

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

const int maxn = 20005, maxe = 100005;

struct node {
    int to, fr, val;
} edge[maxe * 2];
struct node1 {
    int to, nxt, val;
} ed[maxe * 2];

int n, m, tot, K = 15, q;
int f[maxn], head[maxn], deep[maxn], dis[maxn][50], fa[maxn][50];

bool vis[maxn];

bool cmp(node x, node y) { return x.val > y.val; }
void add(int a, int b, int c) {
    tot++;
    ed[tot].to = b;
    ed[tot].val = c;
    ed[tot].nxt = head[a];
    head[a] = tot;
}
int findroot(int x) {
    if (f[x] == x)
        return x;
    return f[x] = findroot(f[x]);
}
void maxtree() {
    for (int i = 1; i <= n; i++) f[i] = i;
    for (int i = 1; i <= m; i++) {
        int x = edge[i].fr;
        int y = edge[i].to;
        int xf = findroot(x), yf = findroot(y);
        if (xf != yf) {
            add(x, y, edge[i].val);
            add(y, x, edge[i].val);
            f[yf] = f[xf];
        }
    }
}
void dfs(int x) {
    vis[x] = true;
    for (int i = head[x]; i; i = ed[i].nxt) {
        int v = ed[i].to;
        if (!vis[v]) {
            deep[v] = deep[x] + 1;
            dis[v][0] = ed[i].val;
            fa[v][0] = x;
            for (int j = 1; j < K; j++) {
                fa[v][j] = fa[fa[v][j - 1]][j - 1];
                dis[v][j] = min(dis[v][j - 1], dis[fa[v][j - 1]][j - 1]);
            }
            dfs(v);
        }
    }
}
int getlca(int x, int y) {
    if (findroot(x) != findroot(y))
        return -1;
    int res = 1e9;
    if (deep[x] < deep[y])
        swap(x, y);
    for (int i = K - 1; i >= 0; i--) {
        if (fa[x][i] != 0 && deep[fa[x][i]] >= deep[y]) {
            res = min(dis[x][i], res);
            x = fa[x][i];
        }
    }
    if (x == y)
        return res;
    for (int i = K - 1; i >= 0; i--) {
        if (fa[x][i] != fa[y][i]) {
            res = min(dis[x][i], res);
            res = min(dis[y][i], res);
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    res = min(dis[x][0], res);
    res = min(dis[y][0], res);
    return res;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        edge[i].to = y;
        edge[i].fr = x;
        edge[i].val = z;
    }
    sort(edge + 1, edge + m + 1, cmp);
    maxtree();
    deep[1] = 1;
    memset(dis, 0x7f, sizeof(dis));
    for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
    scanf("%d", &q);
    for (int i = 1; i <= q; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%d\n", getlca(x, y));
    }
}

P5022 NOIP2018 提高组 旅行

如果在树上,贪心的进入较小的编号的儿子遍历。

在基环树上,发现可以去除一条边,当作树的情况。

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

#define int short

const int maxn=5005;

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

inline int read()
{
    int fx=1,sum=0;
    char c=getchar();
    while(c!='-'&&(c<'0'||c>'9')) c=getchar();
    if(c=='-') fx=-1,c=getchar();
    while('0'<=c&&c<='9') sum=(sum<<3)+(sum<<1)+c-'0',c=getchar();
    return fx*sum;
}

int n,m,flg;

bool vis[maxn],blk[maxn];

vector<int>vec,ans;

inline void dfs(int u)
{
    vis[u]=true;
    vec.emplace_back(u);
    if(ans.size()&&ans[vec.size()-1]<vec.back()&&flg==0) {flg=1;return ;}
    if(ans.size()&&ans[vec.size()-1]>vec.back()&&flg==0) flg=-1;
    vector<int>tmp;
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(vis[v]||blk[T.edge[i].id]) continue;
        tmp.push_back(v);
    }
    sort(tmp.begin(),tmp.end());
    for(auto v:tmp)
    {
        if(vis[v]) continue;
        dfs(v);
        if(flg==1) return ;
    }
}

signed main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        x=read(),y=read();
        T.add(x,y,i),T.add(y,x,i);
    }
    if(m==n)
    {
        for(int i=1;i<=m;i++)
        {
            blk[i]=true;flg=0;
            memset(vis,0,sizeof(vis));
            vec.clear();
            dfs(1);
            blk[i]=false;
            if(vec.size()<n) continue;
            if(ans.size()==0) ans=vec;
            else
            {
                for(int j=0;j<n;j++)
                    if(ans[j]>vec[j]) {ans=vec;break;}
                    else if(vec[j]>ans[j]) break;
            }
        }
    }
    else dfs(1),ans=vec;
    for(auto v:ans) printf("%d ",v);
}

P1315 NOIP2011 提高组 观光公交

使用一次加速惠及到的人,是加速后到目的地无需等待人上车的站(包括第一个需要等待的站)。

倒序枚举每个加速器使用的段,继承维护惠及的人数,选择最大的段放。

放完后暴力维护一次信息。

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

#define int long long

#define pii pair<int,int>
#define fi first
#define se second

const int maxn=1e3+5;

int n,m,k,ans;
int t[maxn],d[maxn],arr[maxn],tot[maxn];

vector<int>vec[maxn];

signed main()
{
    // freopen("P1315_5.in","r",stdin);
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<n;i++) scanf("%lld",&d[i]);
    for(int i=1;i<=m;i++)
    {
        int tim,x,y;
        scanf("%lld%lld%lld",&tim,&x,&y);
        vec[y].push_back(tim);
        t[x]=max(t[x],tim);
        tot[y]++;
    }
    for(int i=1;i<=n;i++) arr[i]=max(arr[i-1],t[i-1])+d[i-1];
    for(int i=1;i<=k;i++)
    {
        int lst=0,ans=0,ansid=0;
        for(int j=n-1;j;j--)
        {
            if(arr[j+1]>t[j+1]) lst+=tot[j+1];
            else lst=tot[j+1];
            if(lst>ans&&d[j]) ans=lst,ansid=j;
        }
        d[ansid]--;
        for(int j=ansid+1;j<=n;j++) arr[j]=max(arr[j-1],t[j-1])+d[j-1];
    }
    for(int i=1;i<=n;i++)
    {
        for(auto v:vec[i]) ans+=max(arr[i]-v,0ll);
    }
    printf("%lld",ans);
}

P7124 Ynoi2008 stcm

题解

P7215 JOISC2020 首都

题解

P7320 PMOI-4 可怜的团主

建出任意一颗生成树,将度为 1 的点成为叶子节点,若叶子节点个数超过 n3 个,满足条件 2

否则将叶子两两任意配对,找到一个没有被覆盖的点 u,在其的任意两棵子树(无根树)内寻找两对配对的叶子节点,设其为 (x1,y1)(x2,y2)

将其配对改为 (x1,y2)(x2,y1)

可以覆盖原先覆盖的点,并新覆盖了点 u,通过这样的操作每次至少减少一个没有被覆盖的点,时间复杂度 O(n2)

所以本题总是有解的。

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

const int maxn=2e3+5;

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

int n,m,ex,tot;
int deg[maxn],leaf[maxn],to[maxn];;

bool vis[maxn],col[maxn];

vector<int>ans;

inline void dfs_tree(int u)
{
    vis[u]=true;
    for(int i=G.head[u];i;i=G.edge[i].nxt)
    {
        int v=G.edge[i].to;
        if(vis[v]) continue;
        T.add(u,v);
        T.add(v,u);
        deg[u]++,deg[v]++;
        dfs_tree(v);
    }
}

inline bool dfs(int u,int ed,int f,int flg)
{
    if(u==ed)
    {
        col[u]=1;
        if(flg&&u!=ex) ans.push_back(u);
        return true;
    }
    bool val=false;
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(v==f) continue;
        if(dfs(v,ed,u,flg))
        {
            col[u]|=1;
            val=true;
            break;
        }
    }
    if(flg&&val&&u!=ex) ans.push_back(u);
    return val;
}
inline int fr(int u,int f)
{
    if(deg[u]==1) return u;
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(v==f) continue;
        return fr(v,u);
    }
}
inline void work(int u,int v)
{
    to[u]=v,to[v]=u;
    dfs(u,v,0,0);
}

int need;

int main()
{
    scanf("%d%d",&n,&m);
    need=(n+5)/6;
    for(int i=1;i<=m;i++)
    {
        int v,u;
        scanf("%d%d",&u,&v);
        G.add(u,v);
        G.add(v,u);
    }
    dfs_tree(1);
    for(int i=2;i<=n;i++)
    {
        if(deg[i]==1) leaf[++tot]=i;
    }
    if(tot>=n/3)
    {
        puts("2");
        for(int i=1;i<=n/3;i++) printf("%d ",leaf[i]);
        return 0;
    }
    if(deg[1]==1) leaf[++tot]=1;
    if(tot&1) T.add(1,++n),deg[n]=1,leaf[++tot]=n,ex=n;
    for(int i=1;i<=n;i+=2)
    {
        work(leaf[i],leaf[i+1]);
    }
    while(1)
    {
        int g,u[3],v[3];
        bool flg=true;
        for(int i=1;i<=n;i++)
        {
            if(!col[i])
            {
                g=i;flg=false;
                break;
            }
        }
        if(flg) break;
        int cnt=0;
        for(int i=T.head[g];i;i=T.edge[i].nxt)
        {
            int gv=T.edge[i].to;
            u[++cnt]=fr(gv,g);v[cnt]=to[u[cnt]];
            if(cnt==2) break;
        }
        work(u[1],v[2]),work(v[1],u[2]);
    }
    puts("1");
    for(int i=1;i<=tot;i++)
    {
        int u=leaf[i];
        int v=to[u];
        if(u<v)
        {
            need--;
            ans.clear();
            dfs(u,v,0,1);
            printf("%d ",(int)ans.size());
            for(auto v:ans) printf("%d ",v);
            putchar('\n');
        }
    }
    for(int i=1;i<=need;i++) printf("1 %d\n",i);
}

P7393 TOCO Round 1 Eternal Star

钦定 k 为树的根,那么 k 需要有 1k1 这些儿子,为了维护树的稳定性,需要保证 k 被儿子代替后答案会更大,假设 p(p<k) 代替 k,此时根 p 与儿子 p 之间会相连,需要将儿子 pp+1 代替。

设有 yp 点,有不等式:

k+yp<p+y(p+1)

解得 y>kp,至少要 kp+1 个儿子。

递归处理即可得到正确的树。

点数过多,考虑减少儿子数量。

建出两个 k1 的树,将其相连,相连后会 k1 会相邻,其中一个 k1 会变成 k

相较于原来少了 k[1,k2] 的儿子。

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

const int maxn=1e6+5;

int cnt;
vector<int>E[maxn];

inline int dfs(int k,int ad)
{
    int u=++cnt;
    if(k==1) return u;
    if(u==1) E[u].push_back(dfs(k-1,1));
    for(int i=1;i<k-(u==1);i++)
    {
        for(int j=1;j<=k-i+1+ad;j++) E[u].push_back(dfs(i,0));
    }
    return u;
}
inline void out(int u){for(auto v:E[u]) printf("%d %d\n",u,v),out(v);}

int main()
{
    int K,x;
    scanf("%d%d",&K,&x);
    dfs(K,0);
    printf("%d\n",cnt);
    out(1);
}

P1084 NOIP2012 提高组 疫情控制

要是 NOIP2024 也这样简单就好了……

最大值最小,二分时间 mid,每个军队尽量向上走到根,如果走不到根就在深度最小的点停下来。

判断与根相连的儿子是否叶子节点都被未走到根的军队完全覆盖。

  • 是,可以不用处理该儿子。
  • 否,根需要向下派军队,这个军队需要满足到根以后至少还可以向下走 w 步,w 为儿子与根相连的边权。当然如果使用这个儿子前往首都的军队,就无须 w 的限制。选择满足条件的军队中可以走的步数最少的军队。

set 实现,复杂度 O(nlog2n)

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

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

const int maxn=5e4+5;

struct Edge
{
    int tot;
    int head[maxn];
    struct edgenode{int to,nxt,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;
    }
}T;

int n,m;
int dep[maxn],fa[maxn][20],p[maxn];

ll dis[maxn][20];

inline void dfs(int u,int f)
{
    dep[u]=dep[f]+1;fa[u][0]=f;
    for(int i=1;i<=log2(dep[u]);i++)
        fa[u][i]=fa[fa[u][i-1]][i-1],dis[u][i]=dis[u][i-1]+dis[fa[u][i-1]][i-1];
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(v==f) continue;
        dis[v][0]=T.edge[i].w;
        dfs(v,u);
    }
}

vector<ll>vec[maxn];
int val[maxn];
inline void dfs_find(int u,int f)
{
    int cnt=0;
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        cnt++;
        int v=T.edge[i].to;
        if(v==f) continue;
        dfs_find(v,u);
        val[u]+=val[v];
    }
    if(cnt==1) val[u]++;
    if(vec[u].size()&&dep[u]>2) val[u]=0;
}
inline bool check(ll mid)
{
    for(int i=1;i<=n;i++) vec[i].clear(),val[i]=0;
    for(int i=1;i<=m;i++)
    {
        int u=p[i];ll sum=mid;
        for(int j=log2(dep[u]);~j;j--)
        {
            if(dis[u][j]<=sum&&dep[fa[u][j]]>=2) sum-=dis[u][j],u=fa[u][j];
        }
        vec[u].push_back(sum);
    }
    dfs_find(1,0);
    if(!val[1]) return true;
    multiset<pli>s,tmp;
    for(int i=T.head[1];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        for(int j=0;j<vec[v].size();j++) s.insert({vec[v][j]-T.edge[i].w,v});
    }
    for(int i=T.head[1];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(val[v])
        {
            tmp.clear();
            for(int j=0;j<vec[v].size();j++)
            {
                auto it=s.find({vec[v][j]-T.edge[i].w,v});
                if(it==s.end()) continue;
                tmp.insert(*it);
                s.erase(it);
            }
            auto itt=tmp.begin();
            auto its=s.lower_bound({T.edge[i].w,0});
            if(itt!=tmp.end())
            {
                if((*itt).fi<=(*its).fi||its==s.end()) tmp.erase(itt);
                else s.erase(its);
                for(auto it=tmp.begin();it!=tmp.end();it++) s.insert(*it);
            }
            else
            {
                if(its==s.end()) return false;
                s.erase(its);
            }
        }
    }
    return true;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        T.add(u,v,w),T.add(v,u,w);
    }
    dfs(1,0);
    scanf("%d",&m);
    for(int i=1;i<=m;i++) scanf("%d",&p[i]);
    ll l=0,r=1e15,ans=-1;
    while(l<=r)
    {
        ll mid=(l+r)>>1;
        if(check(mid)) r=mid-1,ans=mid;
        else l=mid+1;
    }
    printf("%lld",ans);
}

P7054 NWRRC2015 Graph

见 2024.11.05 刷题训练

P2680 NOIP2015 提高组 运输计划

最大值最小问题,二分长度,所有长度大于 mid 的路径,将路径上的边全部标记一次,设总共标记 t 次。

查询是否存在一条边长度为 w 且被标记了 t 次,满足 max(dis)wmid

标记使用树上差分,时间复杂度 O(nlogn)

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

#define int long long

const int N=3e5+5;

int n,m,cf[N];
vector<pair<int,int>> G[N];
int f[N][30],dfn[N],uid[N],tim,val[N],dis[N],dep[N];
struct node {
    int u,v,lca,dis;
}Q[N];

void dfs(int u,int pa)
{
    uid[dfn[u]=++tim]=u;
    dep[u]=dep[f[u][0]=pa]+1;
    for(int i=1;i<=19;i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(auto [v,w]:G[u]){
        if(v==pa) continue;
        dis[v]=dis[u]+w;
        dfs(v,u);
        val[v]=w;
    }
}

int LCA(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;~i;i--)
        if(dep[f[u][i]]>=dep[v])
            u=f[u][i];
    if(u==v) return u;
    for(int i=19;~i;i--)
        if(f[u][i]!=f[v][i])
            u=f[u][i],v=f[v][i];
    return f[u][0];
}

bool check(int md)
{
    memset(cf,0,sizeof(cf));
    int cnt=0,ans=0;
    for(int i=1;i<=m;i++){
        if(Q[i].dis>md){
            cf[Q[i].u]++,cf[Q[i].v]++,cf[Q[i].lca]-=2;
            ans=max(ans,Q[i].dis-md);
            cnt++;
        }
    }
    if(cnt==0) return 1;
    for(int i=n;i>1;i--)
        cf[f[uid[i]][0]]+=cf[uid[i]];
    for(int i=2;i<=n;i++)
        if(cf[i]==cnt&&dis[i]-dis[f[i][0]]>=ans)
            return 1;
    return 0;
}

signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1,u,v,w;i<n;i++){
        cin>>u>>v>>w;
        G[u].emplace_back(v,w);
        G[v].emplace_back(u,w);
    }
    dfs(1,0);
    for(int i=1;i<=m;i++){
        cin>>Q[i].u>>Q[i].v;
        Q[i].lca=LCA(Q[i].u,Q[i].v);
        Q[i].dis=dis[Q[i].u]+dis[Q[i].v]-2*dis[Q[i].lca];
    }
    int l=0,r=1e10,ans=-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))
            ans=mid,r=mid-1;
        else
            l=mid+1;
    }
    cout<<ans<<'\n';
    return 0;
}

P5021 NOIP2018 提高组 赛道修建

二分赛道长度,check 时每个点向父亲上传一条长度小于 mid 的最长的边,儿子上传的路径如果大于等于 mid 直接成为一条赛道,并且贪心的两两拼接剩下的点。

贪心的正确性:如果少拼一条赛道,并且用其中较大值上传,那么上传后也该较大值也最多可以拼接成为一条赛道,不同用途产生的贡献时一样的。

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

const int maxn=5e4+5;

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

int n,m,now;

multiset<int>s[maxn];
inline int dfs(int u,int f)
{
    s[u].clear();
    s[u].insert(0);
    int res=0;
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(v==f) continue;
        res+=dfs(v,u);
        auto it=s[v].end();it--;
        s[u].insert(*it+T.edge[i].w);
    }
    auto it=s[u].end();
    for(it--;it!=s[u].end();it=s[u].end(),it--)
    {
        if(*it>=now) res++,s[u].erase(it);
        else break;
    }
    auto nxt=s[u].begin();
    for(auto it=s[u].begin();it!=s[u].end();it=nxt)
    {
        auto tmp=s[u].lower_bound(now-*it);
        if(tmp==it) tmp++;
        if(tmp==s[u].end()) {nxt=it;nxt++;continue;}
        res++;
        s[u].erase(tmp);
        nxt=it;nxt++;
        s[u].erase(it);
    }
    return res;
}
inline bool check(int mid){now=mid;return dfs(1,0)>=m;}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        T.add(x,y,z),T.add(y,x,z);
    }
    int l=0,r=1e9,ans=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid)) l=mid+1,ans=mid;
        else r=mid-1;
    }
    printf("%d",ans);
}
posted @   彬彬冰激凌  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示