一些简单题(2)(Source : NOIP历年试题+杂题)

P3084 [USACO13OPEN]照片Photo

给出$m$个区间$[l_i,r_i]$覆盖$S=[1,n]$,试确定最大特殊点的数使得这每一个区间覆盖且仅覆盖一个特殊点。

如果无解,输出$-1$ 对于100%的数据$nleq 2 imes 10^5 , mleq 10^5$,数据较弱。

Sol1 : 设$ w_i$ 表示$ [1,i] $特殊点数目,那么对于每个区间的约束,有且仅有$1$个就等价于$w_r-w_{l-1}=1$

我们考虑每一个点只可能是特殊点或不是特殊点,那么就是$0 leq w_i - w_{i-1} leq 1$

又上面的式子$w_r-w_{l-1}=1$也可以化成不等式来表示$1 leq w_r-w_{l-1} leq 1$

也就是$w_r - w_{l-1} leq 1 , w_{l-1} -w_r leq -1$ , 结合$w_{i-1} - w_i leq 0$我们就可以跑差分约束系统了。

把$dist{ i,i-1 } =0 , dist{l-1,r}=1 ,dist{r,l-1}=-1$建图跑SPFA(注意判负环!!!)然后到时间输出-1弹出就可以啦。

复杂度$O($卡时间$)$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<deque>
#define Endl endl;//qwq
using namespace std;
const int maxn=1e6;
int n,m,cnt=1;
int head[maxn],vis[maxn],dis[maxn];
struct node
{
    int nxt;
    int to;
    int w;
}edge[maxn];
void add(int x,int y,int z)
{
    edge[cnt].to=y;edge[cnt].w=z;edge[cnt].nxt=head[x];head[x]=cnt++;
}
//int cn[maxn];
int l,r;
int tot=0;
int spfa()
{
    deque<int> q;//双端队列优化spfa
    memset(dis,0x3f,sizeof(dis));
    dis[0]=0;//从0开始的原因见下面
    vis[0]=1;
    q.push_back(0);
    while(q.size())
    {
        int x=q.front();
        q.pop_front();
        vis[x]=0;
        for(int i=head[x];i;i=edge[i].nxt)
        {
            int y=edge[i].to,z=edge[i].w;
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                //cn[y]=cn[x]+1;
                if(!vis[y])
                {
                    if(++tot>1926817) return -1;//人要有点信仰,这是梦想spfa的核心部分
                    //if(cn[y]>n)return -1;这是正儿八经的判负环,请一定要学会
                    vis[y]=1;
                    if(q.size()&&dis[y]>dis[q.front()])q.push_back(y);
                    else q.push_front(y);
                }
            }
        }
    }
    return dis[n];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>l>>r;
        add(l-1,r,1);
        add(r,l-1,-1);
    }
    for(int i=1;i<=n;i++)
        add(i,i-1,0),add(i-1,i,1);//i-1可能为0,所以上面最短路要从0开始跑(前后呼应~~手动滑稽)
    cout<<spfa()<<Endl;
    return 0;
}
P3084.cpp-某std

Sol2:作为一个不会差分约束的OIer,一个很显然的想法是线性序列上的Dp。

若不考虑-1的情况,这个Dp应该是可以求出结果的。

设$ f_i $ 表示到第$i$个点为止,且第$i$点为特殊点的最大方案数。

转移可以为$f_i = max_{i=l_i}^{r_i} f_j + 1$

主要是求取值范围$l_i,r_i$,对于一条线段$[x,y]$把$l_{y+1} = max(l_{y+1},x) , r_y = min(r_y,x - 1)$

然后由于$l[]$前面比后面范围大,所以逐步紧缩,$l_i = max{l_{i-1},l_i}$

然后由于$r[]$后面比前面范围大,所以逐步紧缩,$r_i = min{r_{i+1},r_i}$

然后做一个dp就可以了(可以单调队列线段树优化转移!!!)

复杂度$O(n log_2 n)/O(n)/O(nm)[玄学随机数据]$

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=2e5+10;
int l[N],r[N],f[N];
int n,m;
signed main()
{
    scanf("%lld%lld",&n,&m);
    for (int i=1;i<=n+1;i++) r[i]=i-1;
    for (int i=1;i<=m;i++) {
        int x,y; scanf("%lld%lld",&x,&y);
        l[y+1]=max(l[y+1],x);
        r[y]=min(r[y],x-1);
    }
    for (int i=2;i<=n+1;i++) l[i]=max(l[i-1],l[i]);
    for (int i=n;i>=1;i--) r[i]=min(r[i+1],r[i]);
    int ans=-1;
    for (int i=1;i<=n;i++) {
        bool st=false;
        for (int j=l[i];j<=r[i];j++)
         f[i]=max(f[i],f[j]+1),st=true;
        if (!st) f[i]=-1;
        ans=max(ans,f[i]); 
    }
    printf("%lld
",ans);
    return 0;
}
P3084-90pts(无优化)
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=2e5+10;
int n,m,l[N],r[N],f[N],q[N],ql,qr;
void add(int x) {
    if (f[x]==-1) return;
    while (qr>=ql&&f[q[qr]]<=f[x]) qr--;
    q[++qr]=x;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for (int i=0;i<=n+1;i++) r[i]=i-1,l[i]=-1;
    for (int i=1;i<=m;i++) {
        int x,y; scanf("%lld%lld",&x,&y);
        r[y]=min(r[y],x-1);
        l[y+1]=max(l[y+1],x-1);
    }
    for (int i=1;i<=n+1;i++) l[i]=max(l[i],l[i-1]);
    for (int i=n;i>=1;i--) r[i]=min(r[i],r[i+1]);
    
    f[0]=0; 
    ql=1,qr=0;
    for (int i=1;i<=n+1;i++) {
        for (int j=r[i-1];j<=r[i];j++)add(j);
        while (qr>=ql&&q[ql]<=l[i]) ql++;
        if (qr<ql) f[i]=-1;
        else f[i]=f[q[ql]]+1;
    }
    printf("%lld
",max(f[n+1]-1,-1ll));  
    return 0;
}
P3084-100pts(单调队列)

 P1967 [NOIP2013D1T3] 货车运输

一个$n$个点$m$个边可重边,可不连通的无向图,$q$个询问,$u -> v$所有路径上最小值最大是多少。

对于100%的数据$nleq 10^5 , mleq 5 imes 10^5 , qleq 3 imes 10^4 $

Sol:一个比较简单的结论,在每一个连通块中所有点对走的路径必然是按照最大生成树上路径走的。

证明如下,设在一个连通块内$dist(u,v) < dist(u,t) + dist(t,v)$那么必然的,如果需要保证$(t,u,v)$连通我可以在之中随意删掉一条边同时加上一条边。

也就是说,构造断开$(u,v)$的这条边加上一条$(u,t)$或者$(v,t)$,这样明显最大生成树要更大,和已知的最大生成树相矛盾。

然后转化为树上问题,可以搞LCA了。

复杂度$O((q+n) {log_2}^2 n)$

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=1e5+10;
struct rec{
    int pre,to,w;
}a[N<<1];
int d[N][25],g[N][25],f[N],head[N],dep[N];
int tot,n,m;
bool vis[N];
struct Edge{ int u,v,w; };
vector<Edge>E;
bool cmp(Edge a,Edge b) { return a.w>b.w;}
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
inline void write(int x)
{
    if (x<0) putchar('-'),x=-x;
    if (x>9) write(x/10);
    putchar('0'+x%10);
}
void adde(int u,int v,int w)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    a[tot].w=w;
    head[u]=tot;
}
int father(int x) {
    if (f[x]==x) return x;
    return f[x]=father(f[x]);
}
void kruskal()
{
    sort(E.begin(),E.end(),cmp);
    for (int i=1;i<=n;i++) f[i]=i;
    for (int i=0;i<E.size();i++) {
        int u=E[i].u,v=E[i].v,w=E[i].w;
        int fu=father(u),fv=father(v);
        if (fu==fv) continue;
        adde(u,v,w); adde(v,u,w);
        f[fu]=fv;
    }
}
void dfs(int u,int fa)
{
    vis[u]=true;
    for (int i=head[u];i;i=a[i].pre) {
        int v=a[i].to; if (v==fa) continue;
        g[v][0]=u;d[v][0]=min(d[v][0],a[i].w);dep[v]=dep[u]+1;
        dfs(v,u);
    }
}
void init()
{
    memset(d,0x3f,sizeof(d));
    memset(vis,false,sizeof(vis));
    for (int i=1;i<=n;i++) if (!vis[i]) dfs(i,0);
    for (int i=1;i<=21;i++)
     for (int j=1;j<=n;j++)
      g[j][i]=g[g[j][i-1]][i-1],
      d[j][i]=min(d[j][i-1],d[g[j][i-1]][i-1]); 
}
int getans(int u,int v)
{
    if (father(u)!=father(v)) return -1;
    int ret=0x3f3f3f3f;
    if (dep[u]<dep[v]) swap(u,v);
    for (int i=21;i>=0;i--)
     if (dep[g[u][i]]>=dep[v]) ret=min(ret,d[u][i]),u=g[u][i];
    if (u==v) return ret;
    for (int i=21;i>=0;i--)
     if (g[u][i]!=g[v][i]) {
         ret=min(ret,d[u][i]);
         ret=min(ret,d[v][i]);
         u=g[u][i]; v=g[v][i];
     }
    ret=min(ret,d[u][0]);
    ret=min(ret,d[v][0]);
    return ret; 
}
signed main()
{
    n=read();m=read();
    for (int i=1;i<=m;i++) {
        int u=read(),v=read(),w=read(); 
        E.push_back((Edge){u,v,w});
    }
    kruskal();
    init();
    int T=read();
    while (T--) {
        int u=read(),v=read();
        write(getans(u,v)); putchar('
');
    }
    return 0;
}
P1967.cpp

P3527 [POI2011]MET-Meteors

有$n$个国家,给定一个含有$m$个节点的环,$k$次事件,每次可以对$[l,r]$之间的节点区间加$d$,

询问至少几次事件后,$n$个国家权值经过多少次事件后获得权值不小于$p_i$。

对于100%的数据 $ n,m,kleq 3 imes 10^5$ , 数据随机。

Sol :离线后整体二分,考虑队列$ [l,r] $中的所有点的答案都在$ [L,R] $之间。

如果当前答案$mid = left lfloor frac{L+R}{2} ight floor$  是不是可以,

如果可以就表示事件发生次数可以越少,否则,事件发生次数必须越多。

这里可以使用一个技巧,类似于莫队的指针now表示当前发生到次数多少了,如果不够往上加,否则往下减。

这样答案可以保证和$r-l$同级的复杂度。

然后使用树状数组维护区间修改,单点查询。

最后注意一下加起来爆longlong问题:一旦超过了$p_i$的权便不再累加。

# include <bits/stdc++.h>
# define int long long
# define inf (0x7f7f7f7f)
using namespace std;
const int N=3e5+10;
struct rec{ int l,r,w;}a[N];
vector<int>Vd[N];
int n,m,T;
int p[N],q[N],t[N],ans[N],cnt[N],q1[N],q2[N];
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
int lowbit(int x){return x&(-x);}
void update(int x,int d){for (;x<=m;x+=lowbit(x)) t[x]+=d;}
int query(int x) { int ret=0; for (;x;x-=lowbit(x)) ret+=t[x]; return ret;}
void modify(int l,int r,int d){update(l,d); update(r+1,-d);}
void change(int l,int r,int w)
{
    if (l<=r) { modify(l,r,w);}
    else { modify(l,m,w); modify(1,r,w);}
}
int now=0;
void solve(int l,int r,int L,int R)
{
    if (l>r||L>R) return;
    if (L==R) {
        for (int i=l;i<=r;i++) ans[q[i]]=L;
        return;
    }
    int mid=(L+R)>>1,t1=0,t2=0;
    while (now<mid) ++now,change(a[now].l,a[now].r,a[now].w);
    while (now>mid) change(a[now].l,a[now].r,-a[now].w),--now;
    for (int i=l;i<=r;i++) {
        cnt[q[i]]=0;
        for (int j=0;j<Vd[q[i]].size();j++) {
            cnt[q[i]]+=query(Vd[q[i]][j]);
            if (cnt[q[i]]>p[q[i]]) break;
        }
    }
    for (int i=l;i<=r;i++) {
        if (cnt[q[i]]>=p[q[i]]) q1[++t1]=q[i];
        else q2[++t2]=q[i]; 
    }
    for (int i=1;i<=t1;i++) q[l+i-1]=q1[i];
    for (int i=1;i<=t2;i++) q[l+t1+i-1]=q2[i];
    solve(l,l+t1-1,L,mid);
    solve(l+t1,r,mid+1,R);
}
signed main()
{
    n=read();m=read();
    for (int i=1;i<=m;i++) Vd[read()].push_back(i);
    for (int i=1;i<=n;i++) p[i]=read(),q[i]=i;
    T=read();
    for (int i=1;i<=T;i++) a[i].l=read(),a[i].r=read(),a[i].w=read();
    T++; a[T].l=1,a[T].r=m,a[T].w=inf;
    solve(1,n,1,T);
    for (int i=1;i<=n;i++) 
        if (ans[i]==T) puts("NIE");
        else printf("%lld
",ans[i]);
    return 0;
}
P3527.cpp

 P4390 [BOI2007]Mokia 摩基亚

给出一个$W imes W$的正方形,支持下面2个操作:

1. $ [x,y,A] $向$ (x,y) $这个格子单点$ +A $

2.$[x1,y1,x2,y2]$ 查询子矩阵权值和,保证 $ (x_1,y_1) $在$ (x_2,y_2) $非严格左下方

对于100%的数据$1 leq Wleq 2 imes 10^6,1 leq 操作数leq 2 imes 10^5$

Sol : 考虑数据范围那么大二维权值线段树或者树状数组会MLE,所以考虑离线的方法。

直接cdq,考虑一个转化,就是每一个格子 (操作的时间,x坐标,y坐标)可以看做一个三元组,直接三维偏序就可以了。

然后对于操作2用容斥原理拆成$ Ans = (x_2,y_2)-(x_2,y_1-1)-(x_1-1,y_2)+(x_1-1,y_1-1)$

注意到这样数组的下标会为0,而lowbit(0) =0 所以整体右移,对应的w的值也需要+1.

# include <bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int tot,w;
int t[N],ans[N],tim[N];
bool mark[N];
int lowbit(int x) { return x&(-x);}
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
void update(int x,int d){x++; for (;x<=w+1;x+=lowbit(x)) t[x]+=d;    }
int query(int x) { int ret=0; x++; for (;x;x-=lowbit(x)) ret+=t[x]; return ret;}
struct node{ int op,id,x,y,w;}a[N],b[N];
struct aaa{ int x,d;}tmtm[N];
int cnt;
void cdq(int l,int r)
{
    if (l==r) return;
    int mid=(l+r)>>1;
    cdq(l,mid); cdq(mid+1,r);
    int t1=l,t2=mid+1;cnt=0;
    for (int i=l;i<=r;i++) {
        if ((t1<=mid&&a[t1].x<=a[t2].x)||t2>r) {
            if (a[t1].op==1) {
                update(a[t1].y,a[t1].w);
                cnt++; tmtm[cnt].x=a[t1].y,tmtm[cnt].d=a[t1].w;
            }
            b[i]=a[t1++];
        } else {
            if (a[t2].op==2) ans[a[t2].id]+=query(a[t2].y);
            b[i]=a[t2++];
        }
    }
    for (int i=1;i<=cnt;i++) update(tmtm[i].x,-tmtm[i].d);
    for (int i=l;i<=r;i++) a[i]=b[i];
}
int main()
{
    memset(mark,false,sizeof(mark)); 
    while (true) {
        int opt=read();
        if (opt==0) w=read();
        else if (opt==1) {
            int x=read(),y=read(),w=read(); 
            a[++tot]=(node) {1,tot,x,y,w}; 
        } else if (opt==2) {
            int x1=read(),y1=read(),x2=read(),y2=read();
            mark[tot+1]=true;
            a[++tot]=(node) {2,tot,x2,y2,0}; 
            a[++tot]=(node) {2,tot,x2,y1-1,0};
            a[++tot]=(node) {2,tot,x1-1,y2,0};
            a[++tot]=(node) {2,tot,x1-1,y1-1,0};
        } else if (opt==3) break;
    }
    cdq(1,tot);
    for (int i=1;i<=tot;i++)
     if (mark[i]) printf("%d
",ans[i]-ans[i+1]-ans[i+2]+ans[i+3]);
    return 0;
}
P4390.cpp

 P1527 [国家集训队]矩阵乘法

给出一个$n imes n$的矩阵,支持询问子矩阵的$K$小值。

对于100%的数据$nleq 500,Qleq 60000$

Sol : 直接整体二分,注意二维树状数组的写法(不要像1维树状数组那么写,中间那一维需要重新开始)。

         对于每一层二分,矩阵中的元素最多被插入到树状数组一次,也就是说最大复杂度是$O(n^2 {log_2}^2 n)$

         对于每一层,每个询问最多只可能被处理一次,即复杂度$O(m  {log_2}^2 n)$

         最后二分的总层数最大是$log_2 INF,INF=10^9$

         离散化后,层数就是严格的$log_2 n$层了,

         复杂度大概是$O((n^2 + Q){log_2}^3 n)$

// luogu-judger-enable-o2
# include <bits/stdc++.h>
using namespace std;
const int N=505;
const int Q=60005;
struct rec{
    int x1,y1,x2,y2,x,y,v,id,op,k;
}a[N*N+Q],q1[N*N+Q],q2[N*N+Q];
int c[N][N],ans[N*N+Q];
int n,m,tot;
# define lowbit(x) (x&(-x))
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar('0'+x%10);
}
void update(int x,int y,int d)
{
    x++; y++;
    for (int i=x;i<=n+1;i+=lowbit(i))
     for (int j=y;j<=n+1;j+=lowbit(j))
      c[i][j]+=d;
}
int query(int x,int y)
{
    x++; y++; int ret=0;
    for (int i=x;i;i-=lowbit(i))
     for (int j=y;j;j-=lowbit(j))
      ret+=c[i][j];
    return ret;  
}
int SubSum(int x1,int y1,int x2,int y2)
{
    return query(x2,y2)-query(x2,y1-1)-query(x1-1,y2)+query(x1-1,y1-1);
}
void solve(int l,int r,int L,int R)
{
    if (l>r||L>R) return;
    if (L==R) {
        for (int i=l;i<=r;i++) ans[a[i].id]=L;
        return;
    }
    int mid=(L+R)/2,t1=0,t2=0;
    for (int i=l;i<=r;i++) {
        if (a[i].op==1) {
            if (a[i].v<=mid) update(a[i].x,a[i].y,1),q1[++t1]=a[i];
            else q2[++t2]=a[i];
        } else if (a[i].op==2) {
            int t=SubSum(a[i].x1,a[i].y1,a[i].x2,a[i].y2);
            if (t>=a[i].k) q1[++t1]=a[i];
            else a[i].k-=t,q2[++t2]=a[i];
        }
    }
    for (int i=1;i<=t1;i++)
     if (q1[i].op==1) update(q1[i].x,q1[i].y,-1);
    for (int i=1;i<=t1;i++) a[l+i-1]=q1[i];
    for (int i=1;i<=t2;i++) a[l+t1+i-1]=q2[i];
    solve(l,l+t1-1,L,mid);
    solve(l+t1,r,mid+1,R);
}
int main()
{
    n=read();m=read();
    for (int i=1;i<=n;i++) 
     for (int j=1;j<=n;j++) {
         tot++;a[tot].op=1;
         a[tot].x=i,a[tot].y=j,a[tot].id=tot;
         a[tot].v=read();
     }
     for (int i=1;i<=m;i++) {
         tot++; a[tot].id=tot; a[tot].op=2;
         a[tot].x1=read();a[tot].y1=read();a[tot].x2=read();
         a[tot].y2=read();a[tot].k=read();
     }
     solve(1,tot,0,1e9);
     for (int i=n*n+1;i<=tot;i++)
      write(ans[i]),putchar('
');
    return 0;
}
P1527.cpp
原文地址:https://www.cnblogs.com/ljc20020730/p/10886386.html