CodeForces 123 C.Brackets(组合数学+dp)

202 篇文章 1 订阅
190 篇文章 1 订阅

Description

要求给一个 n×m 的棋盘上放上左括号或者右括号,使得其满足两个条件,首先,任意一条从 (1,1) (n,m) 的路径(只能往下或往右走)都是一个合法的括号序列,其次,每个位置有一个权值(各不相同),按权值从小到大把位置里的括号拿出来的序列是所有满足第一个条件的方案中字典序第 k 小的,保证有解,输出该序列

Input

第一行三个整数n,m,k,之后输入一个 n×m 的矩阵 p pi,j表示第 i 行第j列元素的权值, pi,j 各不相同

(1n,m100,1k1018,1pi,jnm)

Output

输出一个 n×m 的括号矩阵

Sample Input

1 2 1
1 2

Sample Output

()

Solution

考虑从 (1,1) (n,m) 的两条路径 (1,1)(1,m)(n,m) (1,1)(1,m1)(2,m1)(2,m)(n,m)

这两条路径均为合法括号序列,且前半段和后半段完全一样,区别是第一条路径经过 (1,m) 而第二条路径经过 (2,m1) ,故这两个位置的括号应该相同,类似的可以证明,行列和固定的位置括号都应该相同,也就是说我们只需要考虑 n+m1 个位置的括号

由于在考虑字典序时不是按顺序来排列括号而是按权值来排列,故首先要得到这 n+m1 个位置的权值,对于 x 位置,显然其权值应该是所有满足i+j1=x pi,j 最小值,得到其权值 val[x] 后把这些位置按权值从小往大排序,用类似康托展开的思想来放括号

对于权值第 i 小的位置,前面i1个权值比其小的括号已经放好,我们假设当前位置放左括号,如果得到的方案数 num 不超过 k ,说明该位置确实应该放左括号,否则说明当前位置应该放右括号,且k要减掉 num 表示把当前位置放左括号的那些字典序较小的方案减掉,所以问题变为给出一个部分括号确定的序列,要求出合法括号序列数量

dp[i][j] 表示前 i 个位置左括号比右括号多j个的方案数,对于第 i+1 个位置,如果这个位置之前没有放左括号说明当前这个位置可以放右括号,有转移 dp[i+1][j1]+=dp[i][j] ,如果这个位置之前没有放右括号说明当前这个位置可以左括号,有转移 dp[i+1][j+1]+=dp[i][j] dp[n+m1][0] 即为合法序列个数

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define maxn 205
int n,m,val[maxn],id[maxn]; 
ll k,dp[maxn][maxn];
char ans[maxn];
bool cmp(int a,int b)
{
    return val[a]<val[b];
}
int main()
{
    while(~scanf("%d%d%I64d",&n,&m,&k))
    {
        int N=n+m-1;
        for(int i=1;i<=N;i++)val[i]=n*m,id[i]=i;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                int t;
                scanf("%d",&t);
                val[i+j-1]=min(val[i+j-1],t);
            }
        sort(id+1,id+N+1,cmp);
        memset(ans,0,sizeof(ans));
        for(int l=1;l<=N;l++)
        {
            ans[id[l]]='(';
            memset(dp,0,sizeof(dp));
            dp[0][0]=1;
            for(int i=0;i<N;i++)
                for(int j=i&1;j<=i&&j<=N-i;j+=2)
                    if(dp[i][j])
                    {
                        if(dp[i][j]>k)dp[i][j]=k;
                        if(ans[i+1]!='('&&j)dp[i+1][j-1]+=dp[i][j];
                        if(ans[i+1]!=')')dp[i+1][j+1]+=dp[i][j];
                    }
            if(k>dp[N][0])
            {
                k-=dp[N][0];
                ans[id[l]]=')';
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)printf("%c",ans[i+j-1]);
            printf("\n");
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值