8月29日考试 题解(区间DP+贪心+并查集+博弈论)

T1区间DP写挂了挂掉40分,一开始写的对着呢QAQ。$rk2$变成$rk6$了。

T1合并集合

有一个长度为$n$的呈环状的集合,每个集合记为$S_i$。合并一个集合所产生的分数为$size_i \times size_j$,合并后集合内的元素都是互不相同的。问最大分数。

显然区间DP。套路题,破环成链,然后$n^3$搞就好。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=305;
int a[maxn*2],n,vis[maxn*2],ans,cnt[maxn*2][maxn*2],f[maxn*2][maxn*2];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
signed main()
{
    //freopen("merge.in","r",stdin);
    //freopen("merge.out","w",stdout);
    n=read();
    for (int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i];
    for (int i=1;i<=2*n;i++)
        for (int j=i;j<=2*n;j++)
        {
            memset(vis,0,sizeof(vis));
            for (int k=i;k<=j;k++)
                if (!vis[a[k]]) vis[a[k]]=1,cnt[i][j]++;    
        } 
    for (int len=2;len<=n;len++)
        for (int l=1;l<=2*n-len+1;l++)
        {
            int r=l+len-1;
            for (int k=l;k<=r;k++)
                f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+cnt[l][k]*cnt[k+1][r]);
        }
    for (int i=1;i<=n;i++) ans=max(ans,f[i][i+n-1]); 
    printf("%lld",ans);
    return 0;
}

T2 climb

有一个人在井底,井高为$l$。他现在有$n$种向上爬的方法:每次每天上午上升$a_i$,晚上滑下$b_i$。井内水位一开始是0,每天上升$c_i$。如果如果某天晚上人的高度小于等于水位,那么他会被淹死。问至少在第几天爬出井,如果不能输出$-1$。

同机房大佬的做法太神仙了。我就写了一个贪心然后就A了。说一下贪心策略:

假设之前已经向上爬的高度为$len$,已经过了$day$天。

1.首先挑一个$a_i$最大的方案,看$len+a_i$是否大于等于$l$,如果合法,直接输出当前天数。

2.如果不合法,那么挑一个$a_i-b_i$最大的;如果有很多个最大的,挑一个$a_i$最小的(因为有可能后面利用较大的$a_i$能爬出井)。

3.每天结束后判断一下这个人有没有被淹死即可。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=100005;
int n,c[maxn],water,man,cnt1=1,cnt2=1,vis[maxn],l;
struct node
{
    int x,y,z,id;
}a[maxn];
struct h
{
    int x,y,id;
}b[maxn];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
bool cmp(node a,node b)
{
    if (a.z==b.z) return a.x<b.x;
    return a.z>b.z;
}
bool cmp1(h a,h b)
{
    return a.x>b.x;
}
signed main()
{
    //freopen("climb.in","r",stdin);
    //freopen("climb.out","w",stdout);
    n=read();l=read();
    for (int i=1;i<=n;i++)
    {
        a[i].x=read();a[i].y=read();
        b[i].x=a[i].x;b[i].y=a[i].y;
        a[i].z=a[i].x-a[i].y;
        a[i].id=b[i].id=i;
    }
    for (int i=1;i<=n;i++) c[i]=read();
    sort(a+1,a+n+1,cmp);
    sort(b+1,b+n+1,cmp1);
    for (int i=1;i<=n;i++)
    {
        while(vis[b[cnt2].id]) cnt2++;
        if (man+b[cnt2].x>=l){printf("%lld",i);return 0;}
        else man+=a[cnt1].z,vis[a[cnt1++].id]=1;
        water+=c[i];
        if (man<=water){printf("-1");return 0;}
    }
    printf("-1");
    return 0;
}

T3 coin

给一个$n*m$的棋盘,每个格子上至多只有一个硬币。硬币或正或反。每次可以选择没有被选择过的一行或一列,将上面的所有硬币都翻过来。当所有行或列都被选择过或者硬币全为正面朝上时,游戏结束。现在两个人轮流进行操作,游戏结束时最后执行操作的人将获得$1$分;如果硬币全为正面朝上,那么两个人都将额外获得$2$分。两个人都想让自己的得分尽可能大,且都按最优策略操作。问先手的分数。

首先发现一个显然的事实:如果想让硬币都正面朝上,那么原来正面朝上的硬币应被翻偶数次,反面朝上的硬币应被翻奇数次。

每一行或每一列都可选择翻或不翻,那么对于每个硬币所在的位置$(i,j)$,硬币的状态对应了$i$和$j$有着某种对应关系。我们考虑$2-SAT$的思想:

我们记选为$1$,不选为$0$。对于行$i$和列$j$,它们都可以选或不选,对应过来为$i,i',j,j'$。

如果硬币正面朝上,那么$i$向$j$连边,$i'$向$j'$连边(都是无向边),表示行和列要么都不翻,要么都得翻。

如果硬币反面朝上:则与上面相反。道理一样。

因为是无向边,所以直接并查集维护连通块。无解的情况为一个点选和不选的状态在一个连通块中。此时直接输出$(n+m)\ \mod \ 2$。

如果有解,则我们考虑连通块的性质。因为是无向边,且每个点都有两种状态,所以必有一对连通块是对称(点数相同且颜色相反)的。我们把含$1$个数奇偶性相同的一对连通块称为“方块”,不同的一对记为“三角”。如果三角个数为奇数,那么先手胜;如果为偶数则看方块内$1$的总数。如果为奇数先手必胜;否则先手必败。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
int fa[maxn],n,m,t,size[maxn],vis[maxn],cnt1,cnt2,flag;
char a[105][105];
inline int find(int x)
{
    if (x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}
inline void merge(int x,int y)
{
    int xx=find(x),yy=find(y);
    if (xx==yy) return;
    fa[yy]=xx;
    size[xx]+=size[yy];
}
inline void work(int x)
{
    int y=find(x+n+m);x=find(x);
    vis[x]=vis[y]=1;
    if (size[x]%2!=size[y]%2) cnt1++;
    else cnt2+=(size[x]%2);
}
int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n>>m; cnt1=0,cnt2=0,flag=0;
        for (int i=1;i<=2*(n+m);i++)
        {
            fa[i]=i;
            if (i<=n+m) size[i]=1;
            else size[i]=0;
            vis[i]=0;
        }
        for (int i=1;i<=n;i++)
        {
            scanf("%s",a[i]+1);
            for (int j=1;j<=m;j++)
                if (a[i][j]=='x') merge(i,j+n+n+m),merge(i+n+m,j+n);
                else if (a[i][j]=='o') merge(i,j+n),merge(i+n+m,j+n+n+m);
        }
        for (int i=1;i<=n+m;i++)
            if (find(i)==find(i+n+m)){flag=1;break;}
        if (flag){printf("%d\n",(n+m)%2);continue;}
        else
        {
            for (int i=1;i<=n+m;i++)
                if (find(i)==i) work(i);
            if (cnt1%2==1) printf("3\n");
            else printf("%d\n",2+(cnt2%2));
        }
    }
    return 0;
}

 

posted @ 2020-08-29 16:45  我亦如此向往  阅读(194)  评论(1编辑  收藏  举报