给一个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,4...n分别填充到对应的行和列上,这样就可以使矩阵元素和最大
cpp
#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;
}
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 5⋅105次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,l≤i≤r,问能得到的数组的最大值。
由于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,n...n)。为了达到这个效果,那么需要前置状态为 ( 1 , 2 , 3 ... n − 1 , 0 ) (1,2,3 \dots n-1, 0) (1,2,3...n−1,0),这样MEX才能使得结果最大。那么怎么才能到达这个状态呢,不妨借助一个中间状态 ( n − 1 , n − 1 , n − 1 ... n − 1 , 0 ) (n-1, n-1, n-1 \dots n-1 ,0) (n−1,n−1,n−1...n−1,0),在这个基础上再去转移到 ( n − 2 , n − 2 , n − 2 ... n − 2 , n − 1 , 0 ) (n-2, n-2, n-2 \dots n-2,n-1,0) (n−2,n−2,n−2...n−2,n−1,0),直到 ( 1 , 2 , 3 ... n − 1 , 0 ) (1,2,3 \dots n-1, 0) (1,2,3...n−1,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[i−1]+store[i],j=0∑i−1dp[j]+(i−j)2)
cpp
#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;
}