codeforce#939 (div2) 题解

C. Nene’s Magical Matrix

给一个nxn的矩阵,现在你可以执行一个操作:将数字1-n任意排列,并将这个序列覆盖到矩阵的任意一行或列。操作次数小于 2 n 2n 2n,求矩阵中元素和的最大值。

这个题显然存在一种巧妙的构造方法使得矩阵中元素 a i , j = m a x ( i , j ) a_{i,j} = max(i,j) ai,j=max(i,j)
从n开始到1,将序列 1 , 2 , 3 , 4 … n 1,2,3,4 \dots n 1,2,3,4n分别填充到对应的行和列上,这样就可以使矩阵元素和最大

#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <time.h>
#include <set>
#include <map>
#include <queue>

#define IOS     ios::sync_with_stdio(0);cin.tie(0);
#define mem(A,B) memset(A,B,sizeof(A));
#define rep(index,start,end) for(int index = start;index < end; index ++)
#define drep(index,start,end) for(int index = start;index >= end; index --)

using namespace std;

const int maxn = 512;

int store[maxn][maxn];
int perm[maxn];
void test(int);
bool check(int);
int main() {
    IOS
    
//    rep(i,1,501) test(i);
//    cout<<"finish"<<endl;
    
    int t;
    cin>>t;
    while(t--) {
        int n;
        cin>>n;
        
        rep(i,0,n) rep(j,0,n) store[i][j] = 0;
        rep(i,1,n+1) perm[i-1] = i;
        string str = "";
        rep(i,0,n) {
            str += ' ';
            str += to_string(perm[i]);
        }
        
        int ans = 0,sum = 0;
        drep(i,n,1) {
            sum += i;
            ans += sum;
        }
        ans = ans*2 - sum;
        int _time = 2*n;
        cout<<ans<<' '<<_time<<endl;
        drep(i,n-1,0) {
            // type 1
            cout<<1<<' '<<i+1<<str<<endl;
            // type 2
            cout<<2<<' '<<i+1<<str<<endl;
        }
    }
    
    return 0;
}
void test(int n) {
    rep(i,1,n+1) perm[i-1] = i;
    
    int _time = 0;
    drep(i,n-1,0) {
        rep(col,0,n)
            store[i][col] = perm[col];
        _time ++;
        
        rep(row,0,n)
            store[row][i] = perm[row];
        _time ++;
    }
    if (_time != 2*n) {
        cout<<n<<"!"<<endl;
    }
    if (!check(n)) {
        cout<<"false"<<endl;
        rep(i,0,n) {
            rep(j,0,n) cout<<store[i][j]<<' ';
            cout<<endl;
        }
    }
}
bool check(int n) {
    bool ans = true;
    rep(i,0,n) {
        rep(j,0,n) {
            ans = ans && (store[i][j] == max(i+1,j+1));
        }
        if (!ans) break;
    }
    return ans;
}

D. Nene and the Mex Operator

M E X ( a l , a l + 1 , … , a r ) MEX({a_l,a_{l+1},…,a_r}) MEX(al,al+1,,ar)表示没有出现在这个些数中最小的数。比如 M E X ( 5 , 3 , 4 ) = 2 MEX(5,3,4) = 2 MEX(5,3,4)=2。现在你可以执行不超过 5 ⋅ 1 0 5 5\cdot 10^5 5105次MEX操作,假设每一次对于区间 ( l , r ) (l,r) (l,r)MEX得到的结果是x,令 a i = x , l ≤ i ≤ r a_i = x, l\le i\le r ai=x,lir,问能得到的数组的最大值。

由于MEX得到的是未出现的最小实数,所以是不是看起来MEX只能让结果变小呢。实则不然,比如 M E X ( 0 ) = 1 MEX(0) = 1 MEX(0)=1以及 M E X ( 1 , 0 ) = 2 MEX(1,0) = 2 MEX(1,0)=2。所以不难得出对于长度为n的区间,MEX所能使区间最大值为 n 2 n^2 n2
那么现在我们再来看怎么才能将任意一个长度为n的区间转换为最大值的样式 ( n , n , n … n ) (n,n,n \dots n) (n,n,nn)。为了达到这个效果,那么需要前置状态为 ( 1 , 2 , 3 … n − 1 , 0 ) (1,2,3 \dots n-1, 0) (1,2,3n1,0),这样MEX才能使得结果最大。那么怎么才能到达这个状态呢,不妨借助一个中间状态 ( n − 1 , n − 1 , n − 1 … n − 1 , 0 ) (n-1, n-1, n-1 \dots n-1 ,0) (n1,n1,n1n1,0),在这个基础上再去转移到 ( n − 2 , n − 2 , n − 2 … n − 2 , n − 1 , 0 ) (n-2, n-2, n-2 \dots n-2,n-1,0) (n2,n2,n2n2,n1,0),直到 ( 1 , 2 , 3 … n − 1 , 0 ) (1,2,3 \dots n-1, 0) (1,2,3n1,0)。通过这个流程我们就能构造出最大值。
然后就是关于哪些区间需要我们去构造。因为MEX最大只能得到 n 2 n^2 n2,所以这里可以用dp去计算我们能得到的最大值。官方题解用的是状压dp,但是我没想到,就额外开了一个数组记录前置状态。转移方程
d p [ i ] = m a x ( d p [ i − 1 ] + s t o r e [ i ] , ∑ j = 0 i − 1 d p [ j ] + ( i − j ) 2 ) dp[i] = max(dp[i-1] + store[i], \sum_{j=0}^{i-1} dp[j] + (i-j)^2) dp[i]=max(dp[i1]+store[i],j=0i1dp[j]+(ij)2)

#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <time.h>
#include <set>
#include <map>
#include <queue>

#define IOS     ios::sync_with_stdio(0);cin.tie(0);
#define re      return 0;
#define mem(A,B) memset(A,B,sizeof(A));
#define rep(index,start,end) for(int index = start;index < end; index ++)
#define drep(index,start,end) for(int index = start;index >= end; index --)

using namespace std;

typedef pair<int, int> pii;

const int maxn = 32;
int pow_table[20] = {0,2};

int store[maxn];
int dp[maxn];
int pre_ind[maxn];
int cnt[maxn];
vector<pii> ops;
void dfs(int,int);
inline int mex(int ,int);
void show(int);
int main() {
    IOS
    
    int n;
    cin>>n;
    rep(i,1,n+1) cin>>store[i];
    
    dp[0] = 0;
    rep(i,1,n+1) {
        if (dp[i] < dp[i-1]+store[i]) {
            dp[i] = dp[i-1]+store[i];
            pre_ind[i] = 0;
        }
        rep(j,0,i) {
            if (dp[i] < dp[j] + (i-j)*(i-j)) {
                dp[i] = dp[j] + (i-j)*(i-j);
                pre_ind[i] = i - j;
            }
        }
    }
    
    int pos = n;
    
    
    pos = n;
    while(pos) {
        int interv = pre_ind[pos];
        if (interv) {
            int s = pos - pre_ind[pos] + 1;
            dfs(s, interv);
            pos -= pre_ind[pos];
        } else
            pos --;
        
    }
//    show(n);
    
    cout<<dp[n]<<' '<<ops.size()<<endl;
    for(auto it = ops.begin();it!=ops.end();it++)
        cout<<it->first<<' '<<it->second<<endl;
    return 0;
}
// return len len len ... len
void dfs(int pos,int len) {
    if (len == 1) {
        if (store[pos]) // to 0
            ops.push_back(make_pair(pos, pos));
        // now 0
        ops.push_back(make_pair(pos, pos));
        // after to 1
        store[pos] = 1;
        return;
    }
    
    drep(i, len-1, 1) {
        // i i i .... i X
        dfs(pos, i);
    }
    // 1 2 3 ... n-1 X
    if (store[pos+len-1])
        ops.push_back(make_pair(pos+len-1, pos+len-1));
    store[pos+len-1] = 0;
    
    // 1 2 3 ... n-1 0
    ops.push_back(make_pair(pos, pos+len-1));
    int num = mex(pos, len);
    rep(i,pos, pos+len) store[i] = num;
}
inline int mex(int pos,int len) {
    rep(i,0,maxn) cnt[i] = 0;
    rep(i,pos, pos+len) {
        int num = store[i];
        if (num<maxn) cnt[num] ++;
    }
    int ind = 0;
    while(cnt[ind]) ind ++;
    return ind;
}
void show(int len) {
    rep(i,1,len+1)
        cout<<store[i]<<' ';
    cout<<endl;
}
  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值