Codeforces Round #576 (Div. 1)

Preface

闲来无事打打CF,就近找了场Div1打打

这场感觉偏简单,比赛时艹穿的人都不少,也没有3000+的题

两三个小时就搞完了吧(F用随机水过去了)


A. MP3

题意不好理解,没用翻译看了好久然后就看错了,后来发现是个SB题

考虑我们将数字排序+离散化之后就可以知道一个区间外有多少个数,可以直接计算答案

然后发现这个区间的移动有单调性,因此直接two points扫一下就好了

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=400005;
int n,lim,num,log_[N],a[N],rst[N],L[N],R[N],ans=1e9;
int main()
{
    RI i,pos; for (scanf("%d%d",&n,&lim),lim*=8,i=1;i<=n;++i)
    scanf("%d",&a[i]),rst[i]=a[i],log_[i]=ceil(log2(i));
    sort(a+1,a+n+1); sort(rst+1,rst+n+1); num=unique(rst+1,rst+n+1)-rst-1;
    for (i=1;i<=n;++i) R[a[i]=lower_bound(rst+1,rst+num+1,a[i])-rst]=i;
    for (i=n;i;--i) L[a[i]]=i; for (i=pos=1;i<=num;++i)
    {
        while (pos<=i&&1LL*log_[i-pos+1]*n>lim) ++pos;
        ans=min(ans,L[pos]-1+n-R[i]);
    }
    return printf("%d",ans),0;
}

B. Welfare State

题目大意就是单点修改和全局取\(\max\)(将所有数与一个数取\(\max\)),输出最后的序列

理解题意就很简单,我们记录下一个点最后一次被单点修改的值与时间,然后在所有全局取\(\max\)的操作中找一个时间在它后面的最大值尝试更新这个值即可

后缀\(\max\)+二分即可完成

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,x,q,opt,y,tim[N],lst[N],et[N],val[N],mx[N],tot;
int main()
{
    //freopen("B.in","r",stdin); freopen("B.out","w",stdout);
    RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&lst[i]);
    for (scanf("%d",&q),i=1;i<=q;++i)
    {
        scanf("%d%d",&opt,&x); if (opt^1) et[++tot]=i,val[tot]=x;
        else scanf("%d",&y),tim[x]=i,lst[x]=y;
    }
    for (i=tot;i;--i) mx[i]=max(mx[i+1],val[i]); for (i=1;i<=n;++i)
    printf("%d ",max(lst[i],mx[lower_bound(et+1,et+tot+1,tim[i])-et]));
    return 0;
}

C. Matching vs Independent Set

思博题,我都想了些什么鬼东西啊

考虑那\(3n\)个点一定有着什么限制,那么我们考虑以下算法:

  1. 扫一遍所有边,若这条边的两个端点都没有被匹配过就匹配这条边,记此时匹配过的边数为\(cnt\)
  2. \(cnt\ge n\)则得出了一个合法匹配
  3. 否则在剩下的点中任取\(n\)个就是独立集

考虑这个算法的正确性,显然当\(cnt\ge n\)时能得出合法匹配,故若\(cnt<n\),则剩下的两两无边相连的点数就是\(3n-2\cdot cnt>n\),因此一定能得到答案

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=3e5+5;
int t,n,m,x,y,eg[N],cnt; bool vis[N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        RI i; for (scanf("%d%d",&n,&m),cnt=0,i=1;i<=m;++i)
        scanf("%d%d",&x,&y),!vis[x]&&!vis[y]&&(vis[x]=vis[y]=1,eg[++cnt]=i);
        if (cnt>=n)
        {
            puts("Matching"); for (i=1;i<=n;++i)
            printf("%d%c",eg[i]," \n"[i==cnt]);
        } else
        {
            puts("IndSet"); for (cnt=0,i=1;i<=3*n&&cnt<n;++i)
            if (!vis[i]) printf("%d ",i),++cnt; putchar('\n');
        }
        for (i=1;i<=3*n;++i) vis[i]=0;
    }
    return 0;
}

D. Rectangle Painting 1

首先我们考虑两个显而易见的结论:

  1. 若一个子矩阵全为白显然无需染色
  2. 若一个子矩阵全为黑直接染完是最优的(即拆成小块永远没有直接染优)

那么我们把这两种情况作为DP的终止条件就可以DP了,设\(f_{x1,y1,x2,y2}\)表示\((x1,y1)-(x2,y2)\)的子矩阵全部染白的最小代价,转移的之后考虑断开行或列分割成两个更小的矩阵转移即可

记忆化搜索实现DP,二维前缀和处理下矩阵状态即可

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=55;
int n,pt[N][N],f[N][N][N][N]; char ch;
inline void get_char(char& ch)
{
    while (isspace(ch=getchar()));
}
inline int getpt(CI x1,CI y1,CI x2,CI y2)
{
    return pt[x2][y2]-pt[x2][y1-1]-pt[x1-1][y2]+pt[x1-1][y1-1];
}
inline int DP(CI x1,CI y1,CI x2,CI y2)
{
    if (~f[x1][y1][x2][y2]) return f[x1][y1][x2][y2];
    if (getpt(x1,y1,x2,y2)==(x2-x1+1)*(y2-y1+1))
    return f[x1][y1][x2][y2]=max(x2-x1+1,y2-y1+1);
    if (!getpt(x1,y1,x2,y2)) return f[x1][y1][x2][y2]=0;
    RI i; int ret=max(x2-x1+1,y2-y1+1);
    for (i=x1;i<x2;++i) ret=min(ret,DP(x1,y1,i,y2)+DP(i+1,y1,x2,y2));
    for (i=y1;i<y2;++i) ret=min(ret,DP(x1,y1,x2,i)+DP(x1,i+1,x2,y2));
    return f[x1][y1][x2][y2]=ret;
}
int main()
{
    RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) for (j=1;j<=n;++j)
    get_char(ch),pt[i][j]=pt[i-1][j]+pt[i][j-1]-pt[i-1][j-1]+(ch=='#');
    return memset(f,-1,sizeof(f)),printf("%d",DP(1,1,n,n)),0;
}

E. Rectangle Painting 2

和上面一题类似,只不过把数据范围加大了,然后代价变成了取\(\min\)

又有一个显而易见的结论此时每次选择的都是一整行或一整列,代价为\(1\)

因此我们考虑将每个黑点的行节点向列节点连一条流量为\(\infty\)的边,然后从源点向行节点连流量为\(1\)的边,从列节点向汇点连流量为\(1\)的边,这样整张图的最小割即为答案(至少要割去一个黑点的行或列节点)

但是现在黑点给出的是一个矩阵的形式,这也很简单,我们将区间化为左开右闭,然后离散化一下,建图的时候源点到行节点连接这一段经过行的数目,到汇点的同理

然后跑一下最大流就是答案

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=205,M=200005,INF=1e9;
int n,m,l1[N],r1[N],l2[N],r2[N],rstl[N],rstr[N],numl,numr;
struct edge
{
    int to,nxt,v;
}e[M]; int head[N],cnt=1,s,t;
inline int findl(CI x)
{
    return lower_bound(rstl+1,rstl+numl+1,x)-rstl;
}
inline int findr(CI x)
{
    return lower_bound(rstr+1,rstr+numr+1,x)-rstr;
}
inline void addedge(CI x,CI y,CI v)
{
    e[++cnt]=(edge){y,head[x],v}; head[x]=cnt;
    e[++cnt]=(edge){x,head[y],0}; head[y]=cnt;
}
#define to e[i].to
class Network_Flow
{
    private:
        int q[N],dep[N],cur[N];
        inline bool BFS(CI s,CI t)
        {
            RI H=0,T=1; memset(dep,0,t+1<<2); q[dep[s]=1]=s;
            while (H<T)
            {
                int now=q[++H]; for (RI i=head[now];i;i=e[i].nxt)
                if (e[i].v&&!dep[to]) dep[to]=dep[now]+1,q[++T]=to;
            }
            return dep[t];
        }
        inline int DFS(CI now,CI tar,int dis)
        {
            if (now==tar) return dis; int ret=0;
            for (RI& i=cur[now];i;i=e[i].nxt)
            if (e[i].v&&dep[to]==dep[now]+1)
            {
                int dist=DFS(to,tar,min(dis,e[i].v));
                dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist;
            }
            if (ret) dep[now]=0; return ret;
        }
    public:
        inline int Dinic(CI s,CI t,int ret=0)
        {
            while (BFS(s,t)) memcpy(cur,head,t+1<<2),ret+=DFS(s,t,INF); return ret;
        }
}NF;
#undef to
int main()
{
    RI i,j,k; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
    scanf("%d%d%d%d",&l1[i],&r1[i],&l2[i],&r2[i]),
    --l1[i],rstl[++numl]=l1[i],rstl[++numl]=l2[i],
    --r1[i],rstr[++numr]=r1[i],rstr[++numr]=r2[i];
    sort(rstl+1,rstl+numl+1); numl=unique(rstl+1,rstl+numl+1)-rstl-1;
    sort(rstr+1,rstr+numr+1); numr=unique(rstr+1,rstr+numr+1)-rstr-1;
    for (i=1;i<numl;++i) addedge(s,i,rstl[i+1]-rstl[i]);
    for (t=numl+numr+1,i=1;i<numr;++i) addedge(numl+i,t,rstr[i+1]-rstr[i]);
    for (i=1;i<=m;++i)
    {
        l1[i]=findl(l1[i]); r1[i]=findr(r1[i]);
        l2[i]=findl(l2[i]); r2[i]=findr(r2[i]);
        for (j=l1[i];j<l2[i];++j) for (k=r1[i];k<r2[i];++k)
        addedge(j,numl+k,INF);
    }
    return printf("%d",NF.Dinic(s,t)),0;
}

F. GCD Groups 2

讲道理我并不知道这题正解是什么,当时开个玩笑写个随机就过了

考虑我们随机从未选取的数中选择一个数,如果这个数扔到第一个集合中可以使当前的\(\gcd\)变小就扔进去

然后剩下的数都放进第二个集合里,我们发现这样的正确性显然,因为加入的数越多\(\gcd\)一定不会变大

但是随机的正确率我并不会证,大致理解了一下就是要求一个集合内存在一个质因子能整除每个数\(\gcd\)才不为\(1\),由于数的分布比较均匀因此质因子分布也很均匀

所以我们直接随机做,然后卡时\(0.45s\)的时候退出输\(NO\)即可

#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,p[N],a[N],g1,g2,tp; bool cs[N];
inline int gcd(CI n,CI m)
{
    return m?gcd(m,n%m):n;
}
int main()
{
    //freopen("F.in","r",stdin); freopen("F.out","w",stdout);
    RI i; for (srand(20030909),scanf("%d",&n),i=1;i<=n;++i)
    scanf("%d",&a[i]),p[i]=i; while (1.0*clock()/CLOCKS_PER_SEC<=0.45)
    {
        for (random_shuffle(p+1,p+n+1),g2=0,g1=a[p[1]],cs[p[1]]=1,i=2;i<=n;++i)
        if (g1!=1&&(tp=gcd(g1,a[p[i]]))<g1) cs[p[i]]=1,g1=tp; else g2=gcd(g2,a[p[i]]);
        if (g1==1&&g2==1)
        {
            for (puts("YES"),i=1;i<=n;++i) printf("%d ",cs[i]+1); exit(0);
        }
        for (i=1;i<=n;++i) cs[i]=0;
    }
    return puts("NO"),0;
}

Postscript

讲道理这场CF难度确实不大,不过也算是有挺多思维好题的吧

今后得多做做这样的比赛QWQ

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/11345223.html