2020 2.1 lxs Contest #113

https://file.floj.tech/export/eoAlAVCaKW099bc9d509

t1

【题解】

题目求亮着的灯有几个连续段。

答案可以转化为亮着的灯个数减去相邻的亮着的灯的对数。

考虑如何求相邻亮着的灯的对数。

求出每种颜色相邻哪几种颜色并连边,权值为这两种颜色相邻对数。

则答案就是总点数减去亮着的点的图的边权和。

由于某些颜色出度较多,直接枚举要T。

所以对于所有出度小于sqrt n的点暴力枚举修改并记录他们与出度大的点造成的贡献,在该出度大的点亮时在加上该贡献。

对于出度大的点只枚举他与其他出度大的点的边即可。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,q,a[N],dahu[N],kuaida,siz[N],jia[N];
struct pigu
{
    int q,dao;
    pigu(int x=0,int y=0)
    {
        dao=x;q=y;
    }
    friend bool operator <(pigu x,pigu y)
    {
        return x.dao<y.dao;
    }
};
vector <pigu> ma[N],hu,ma2[N];
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
bool book[N],xiao[N];
int main()
{
//    freopen("light.in","r",stdin);
//    freopen("light.out","w",stdout);
    n=read();m=read();q=read();kuaida=sqrt(n);
    for(int i=1;i<=n;i++) a[i]=read();
    int tot=0;
    for(int i=1;i<=n;i++) if(a[i]!=a[i-1]) dahu[++tot]=a[i];
    for(int i=1;i<=tot;i++) a[i]=dahu[i],siz[a[i]]++;
    n=tot;
    for(int i=2;i<=tot;i++) ma[a[i]].push_back(pigu(a[i-1],1)),ma[a[i-1]].push_back(pigu(a[i],1));
    for(int i=1;i<=m;i++)
    {
        int len=ma[i].size();
        if(!len) continue;
        sort(ma[i].begin(),ma[i].end());
        int ge=0;
        hu.clear();
        hu.push_back(pigu(ma[i][0].dao,1));
        for(int j=1;j<len;j++)
        {
            if(ma[i][j].dao==ma[i][j-1].dao) hu[ge].q++;
            else ge++,hu.push_back(pigu(ma[i][j].dao,1));
        }
        ma[i].clear();
        for(int j=0;j<=ge;j++) ma[i].push_back(hu[j]);
    }
    for(int i=1;i<=m;i++)
    {
        int len=ma[i].size();
        if(len<kuaida) xiao[i]=1;
    }
    for(int i=1;i<=m;i++)
        if(xiao[i]==0)
        {
            int len=ma[i].size();
            for(int j=0;j<len;j++)
                if(xiao[ma[i][j].dao]==0)
                    ma2[i].push_back(ma[i][j]);
        }
    int ans=0,jian=0;
    while(q--)
    {
        int x=read();
        int pan=book[x]?-1:1;
        book[x]^=1;
        ans+=siz[x]*pan;
        if(xiao[x]==1)
        {
            int len=ma[x].size();
            for(int i=0;i<len;i++)
            {
                if(xiao[ma[x][i].dao]==0) jia[ma[x][i].dao]+=ma[x][i].q*pan;
                if(book[ma[x][i].dao]==0) continue;
                jian+=ma[x][i].q*pan;
            }
        }
        else
        {
            int len=ma2[x].size();
            for(int i=0;i<len;i++)
            {
                if(book[ma2[x][i].dao]==0) continue;
                jian+=pan*ma2[x][i].q;
            }
            jian+=pan*jia[x];
        }
        cout<<ans-jian<<"
";
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
View Code

t2

【题解】

可以对于每个相邻的不为0的点连边边权为他们的值的差。发现如果形成了环,这环上权值和必定为周期的倍数。

所以就dfs找环,找到就求gcd即可。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,now[100005],dis[100005],last[100005],size;
struct pigu
{
    int dao,ne,quan;
}a[200005];
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
inline void lingjiebiao(int x,int y,int z)
{
    a[++size].dao=y;
    a[size].quan=z;
    a[size].ne=last[x];
    last[x]=size;
}
inline int gcd(int x,int y)
{
    if(x<0) x=-x;if(y<0) y=-y;
    if(x==0||y==0) return x|y;
    return x%y==0?y:gcd(y,x%y);
}
int ans=0;
bool book[100005];
inline void dfs(int no,int d)
{
    if(book[no])
    {
        ans=gcd(ans,d-dis[no]);
        return;
    }
    book[no]=1;
    dis[no]=d;
    for(int i=last[no];i;i=a[i].ne)
    {
        dfs(a[i].dao,d+a[i].quan);
    }
}
int main()
{
//    freopen("crossing.in","r",stdin);
//    freopen("crossing.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int sh=0;
        for(int j=1;j<=n;j++)
        {
            now[j]=read();
            if(now[j]==0) continue;
            if(sh) lingjiebiao(sh,j,now[j]-now[sh]),lingjiebiao(j,sh,now[sh]-now[j]);
            sh=j;
        }
    }
    for(int i=1;i<=n;i++) if(!book[i]) dfs(i,0);
    if(!ans) cout<<"-1";
    else cout<<ans;
    fclose(stdin);fclose(stdout);
    return 0;
}
/*
4 5
0 33 0 36
0 4 0 7
42 2 42 5
0 21 0 24
8 0 8 54
*/
View Code

t3

【题解】

发现到了最后图就分成很多块,每块中都有一部分人可以在自己块中乱走,但人数不够走到前头。

定义f[i][j]表示在第i个密室内有j个人时,前头最多有好多个人。在转移时必须保证右边的人不能走到左边来,都是左边人走到右边或者右边有一些人但走不过来,否则就会有m个人在1中。

注意下文=都是表示取max。

当j<a[i]时,在下一间有b[i]个人时,这些人可以走过去,f[i+1][b[i]+j]=f[i][j]+b[i]。

当j<a[i]时,设mx=max(f[i][j]),则下一间本身有<b[i]个人时,f[i+1][j]=mx+j。

当a[i]≤j<a[i]+b[i]时,可以走j-a[i]个人到下一间,f[i+1][j-a[i]]=f[i][j]。

当j>=a[i]+b[i]时,所有人都可以到下一间,f[i+1][j]=f[i][j]。

还有一个性质就是任意一间密室的人不能多于前面最大的a[i]+b[i],否则人全部跑了。这就保证了j<=20000。可以过。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int f[N][20005],da,n,m,a[N],b[N];
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
int main()
{
    //freopen("escape.in","r",stdin);
    //freopen("escape.out","w",stdout);
    n=read();m=read();
    int ans=0;
    for(int i=1;i<=n-1;i++)
        a[i]=read(),b[i]=read();
    da=m-1;
    for(int i=0;i<=m-1;i++) f[1][i]=i;
    for(int i=1;i<=n-1;i++)
    {
        int yj=0;
        for(int j=0;j<a[i];j++) f[i+1][b[i]+j]=max(f[i+1][b[i]+j],f[i][j]+b[i]),yj=max(yj,f[i][j]);
        for(int j=0;j<b[i];j++) f[i+1][j]=max(f[i+1][j],yj+j);
        for(int j=a[i];j<a[i]+b[i];j++) f[i+1][j-a[i]]=max(f[i+1][j-a[i]],f[i][j]);
        for(int j=a[i]+b[i];j<=da;j++) f[i+1][j]=max(f[i+1][j],f[i][j]);
        da=max(da,a[i]+b[i]-1);
    }
    ans=0;
    for(int i=0;i<=da;i++) ans=max(ans,f[n][i]);
    cout<<ans;
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/betablewaloot/p/12250393.html