[贪心专题]CF549G,CF351E,CF226D,CF1276C,CF1148E,CF798D

T1:CF1276C Beautiful Rectangle

title

solution

在这里插入图片描述保证不重复,我们就按对角线这样填下去就可以了,这个很好想到
但注意行,列的变换不要这么写,因为可能是特殊的正方形

r = ( r + 1 ) % row;
c = ( c + 1 ) % col;

接下来返回考虑怎样确定这个矩阵的大小 r o w , c o l row,col row,col,要求 r o w < = c o l row<=col row<=col
我们可以暴力枚举 r o w row row
也就是说每一个数的最多出现次数要求一定小于等于 r o w row row,不然肯定有一行会有重复数字
然后我们把这些数字出现次数加起来,就能计算出列
注意在判断矩阵大小的时候必须 r o w ∗ c o l row*col rowcol来判断,不能单纯按统计的数字最多出现次数
因为不一定能整除 r o w row row,所以有些数不一定达到了上限的

code

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 400005
vector < pair < int, int > > g;
vector < vector < int > > ans;
vector < int > num;
int n, row, col;
int a[MAXN], b[MAXN], cnt[MAXN];

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &a[i] ), b[i] = a[i];
	sort( a + 1, a + n + 1 );
	int m = unique( a + 1, a + n + 1 ) - a - 1;
	for( int i = 1;i <= n;i ++ ) b[i] = lower_bound( a + 1, a + m + 1, b[i] ) - a;
	for( int i = 1;i <= n;i ++ ) cnt[b[i]] ++;
	for( int i = 1;i <= m;i ++ ) g.push_back( make_pair( cnt[i], i ) );
	sort( g.begin(), g.end() );
	int now = 0;
	for( int i = 1;i <= sqrt( n );i ++ ) {
		int temp = 0;
		for( int j = 0;j < g.size();j ++ )
			temp += min( g[j].first, i );
		if( temp / i < i ) continue;
		else if( temp / i * i > now ) now = temp / i * i, row = i, col = temp / i;
	}
	printf( "%d\n%d %d\n", row * col, row, col );
	reverse( g.begin(), g.end() );
	for( int i = 0;i < g.size();i ++ ) {
		for( int j = 1;j <= min( g[i].first, row );j ++ )
			num.push_back( g[i].second );
	}
	ans.resize( row );
	for( int i = 0;i < row;i ++ ) ans[i].resize( col );
	int r = 0, c = 0;
	for( int i = 0;i < num.size();i ++ ) {
		ans[r][c] = num[i];
		r ++, c ++;
		if( r == row ) r = 0, c -= row - 1;
		if( c < 0 ) c += col;
		if ( c >= col ) c -= col;
	}
	for( int i = 0;i < row;i ++ ) {
		for( int j = 0;j < col;j ++ )
			printf( "%d ", a[ans[i][j]] );
		printf( "\n" );
	}
	return 0;
}

T2:CF226D The table

title

solution

本质就是个暴力
对于总和小于零的每一行,每一列都进行取反操作即可
当某一行或某一列操作次数为偶数的时候,其实相当于是无效操作,最后输出答案的时候判掉即可
为什么这么暴力就可以?——因为我们限制了操作的总次数极限就是 1 e 6 1e6 1e6
把每一行和每一列的和加起来等于整个矩阵的总和的两倍,值域 [ − 4 e 6 , 4 e 6 ] [-4e6,4e6] [4e6,4e6]
每次取反至少是 − 1 = > 1 -1=>1 1=>1,整个矩阵总和的两倍至少增加 4 4 4,当和无法再增加的时候就是每一行每一列都大于零

code

#include <cstdio>
#include <vector>
using namespace std;
#define MAXN 105
vector < int > r, c;
int n, m;
int row[MAXN], col[MAXN], visr[MAXN], visc[MAXN];
int a[MAXN][MAXN];

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= m;j ++ ) {
			scanf( "%d", &a[i][j] );
			row[i] += a[i][j];
			col[j] += a[i][j];
		}
	while( 1 ) {
		bool flag = 1;
		for( int i = 1;i <= n;i ++ )
			if( row[i] < 0 ) {
				visr[i] ++;
				row[i] = - row[i];
				for( int j = 1;j <= m;j ++ )
					col[j] -= ( a[i][j] << 1 ), a[i][j] = - a[i][j];
				flag = 0;
				break;
			}
		for( int i = 1;i <= m;i ++ ) 
			if( col[i] < 0 ) {
				visc[i] ++;
				col[i] = - col[i];
				for( int j = 1;j <= n;j ++ )	
					row[j] -= ( a[j][i] << 1 ), a[j][i] = - a[j][i];
				flag = 0;
				break;
			}
		if( flag ) break;
	}
	for( int i = 1;i <= n;i ++ )
		if( visr[i] & 1 ) r.push_back( i );
	for( int i = 1;i <= m;i ++ )
		if( visc[i] & 1 ) c.push_back( i );
	printf( "%d", r.size() );
	for( int i = 0;i < r.size();i ++ )
		printf( " %d", r[i] );
	printf( "\n%d", c.size() );
	for( int i = 0;i < c.size();i ++ )
		printf( " %d", c[i] );
	return 0;
}

T3:CF549G Happy Line

title

solution

非常巧的一个点——对于第 i i i个数,不管它以后的位置在哪,它原来的下标 i i i值与 v a l val val的和是个定值
所以贪心就想到总和越大的越应该往后放
这样才有可能不减
如果有一个数 i i i的总和大于最后一个数的总和且在前面某个位置,那么数 i i i的值就等于总和减去现在的下标,本来值就比最后一个大,减去的又比最后一个小,自然就不可能成为不减序列

code

#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 200005
struct node {
	int val, id;
}v[MAXN];
int n;

bool cmp( node x, node y ) {
	return x.val + x.id < y.val + y.id;
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &v[i].val ), v[i].id = i;
	sort( v + 1, v + n + 1, cmp );
	for( int i = 1;i <= n;i ++ )
		if( v[i].id + v[i].val == v[i - 1].id + v[i - 1].val )
			return ! printf( ":(" );
		else if( i > v[i].id + v[i].val ) return ! printf( ":(");
	for( int i = 1;i <= n;i ++ )
		printf( "%d ", v[i].id + v[i].val - i );
	return 0;
}

T4:CF798D Mike and distribution

title

solution

2 ∗ ∑ i = 1 x a [ p [ i ] ] > ∑ j = 1 n a [ j ] , 2 ∗ ∑ i = 1 x b [ p [ i ] ] > ∑ j = 1 n b [ j ] 2*\sum_{i=1}^xa[p[i]]>\sum_{j=1}^na[j],2*\sum_{i=1}^xb[p[i]]>\sum_{j=1}^nb[j] 2i=1xa[p[i]]>j=1na[j],2i=1xb[p[i]]>j=1nb[j]
不管是 a a a还是 b b b数组,都减去一倍已选的数
其实最后的要求都转化为了, a , b a,b a,b数组已选的数的和要大于没选的数的和
⌊ n 2 ⌋ + 1 \lfloor \frac{n}{2}\rfloor+1 2n+1这个特别要求入手
考虑分奇偶讨论
n = 2 k n=2k n=2k
a a a的从大到小排序,并两两分组
第一组的全选上,之后的每一组选择 b b b较大的一个
接下来证明这个算法的正确性
我们每一组都选的 b b b较大的一个,自然可以压制另一个的 b b b b b b的要求已经达到
那么 a a a怎么办呢?
如果较大的 b b b a a a也较大,那自然更好,甭考虑
如果是较小的 a a a,也不用害怕,因为我们第一组的两个 a a a都选了
它们一定大于这一组里面较大的 a a a,可以压制
那么后面遇到类似情况怎么办,最大的两个 a a a已经用了,没关系
两个 a a a用了,一定解救出前面较大的某些(个) a a a,拿来压制现在的 a a a
就这么一个一个压制下去,最后让 a a a也达到要求
奇数同理可得,不再重复

code

#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 100005
struct node {
	int a, b, id;
}v[MAXN];
int n;

bool cmp( node x, node y ) {
	return x.a > y.a;
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &v[i].a ), v[i].id = i;
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &v[i].b );
	sort( v + 1, v + n + 1, cmp );
	printf( "%d\n", ( n >> 1 ) + 1 );
	if( n % 2 ) {
		printf( "%d ", v[1].id );
		for( int i = 2;i <= n;i += 2 )
			if( v[i].b > v[i + 1].b ) printf( "%d ", v[i].id );
			else printf( "%d ", v[i + 1].id );
	}
	else {
		printf( "%d %d ", v[1].id, v[2].id );
		for( int i = 3;i <= n;i += 2 )
			if( v[i].b > v[i + 1].b ) printf( "%d ", v[i].id );
			else printf( "%d ", v[i + 1].id );
	}
	return 0;
}

T5:CF351E Jeff and Permutation

title

solution

考虑数 a , b a,b a,b,且 ∣ a ∣ > ∣ b ∣ |a|>|b| a>b
a a a + + +
如果 b b b a a a前面,不管 b b b ± ± ±都不会有逆序对
如果 b b b a a a后面,不管 b b b ± ± ±都会有逆序对
a a a − -
如果 b b b a a a前面,不管 b b b ± ± ±都不会有逆序对
如果 b b b a a a后面,不管 b b b ± ± ±都会有逆序对

我们发现,逆序对的产生只与两个数的相对位置以及较大数的正负有关

所以我们只需要考虑对于下标为 i i i的数,前面绝对值比他小的有多少个,后面比他小的有多少个,取个 m i n min min即可
至于比他大的数自然是交个比他大的数来管他们之间是否会有逆序对,不管这个数取正取负,逆序对都在较大数的决定上
所以彼此是相互独立的,不会造成影响

那么两个数的绝对值一样的时候呢?这个时候的正负不就有影响了吗?
所以我们该定义一个 d p [ i ] [ j ] : dp[i][j]: dp[i][j]: i i i个数有 j j j个为 − - ,然后进行转移
其实并不需要,可以证明绝对值一样的数一定会是前面一段全选负后面一段全是正
假设下标 i < j i<j i<j ∣ a [ i ] ∣ = ∣ a [ j ] ∣ |a[i]|=|a[j]| a[i]=a[j]
如果 i i i选了正,意味着数列 [ 1 , i ) [1,i) [1,i)个绝对值比 i i i小的个数多于 ( i , n ] (i,n] (i,n]的个数
那么 j j j而言,前面的数已经包含了 [ 1 , i ) [1,i) [1,i),还多加了一段 ( i , j ) (i,j) (i,j),那么前面绝对值小于它的数个数只会增加(不变)不会减小,后面 ( j , n ] (j,n] (j,n]则只会减小(不变)不会增大
所以是不会彼此之间产生逆序对的,按照上面每个数独立判断的做法做即可

code

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
#define MAXN 2005
int n, ans;
int p[MAXN];

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &p[i] );
	for( int i = 1;i <= n;i ++ ) {
		int Left = 0, Right = 0;
		for( int j = 1;j < i;j ++ )
			if( fabs( p[i] ) <= fabs( p[j] ) ) continue;
			else Left ++;
		for( int j = i + 1;j <= n;j ++ )
			if( fabs( p[i] ) <= fabs( p[j] ) ) continue;
			else Right ++;
		ans += min( Left, Right );
	}
	printf( "%d", ans );
	return 0;
}

T6:CF1148E Earth Wind and Fire

title

solution

很常规的贪心思想,按值排序后,然后下标一一对应,即 s [ i ] s[i] s[i]负责成为 t [ i ] t[i] t[i]
然后我们从最大的开始往下判断,要知道 s s s一定先把多余的可以传递的传递给临近的
因为如果 i i i把多余的分给离他很远的 s [ j ] s[j] s[j],则 s [ j ] < = s [ i + 1 ] s[j]<=s[i+1] s[j]<=s[i+1]
就算加上了 s [ i ] s[i] s[i]给的 x x x s [ j ] , s [ i + 1 ] s[j],s[i+1] s[j],s[i+1]之间的差距也不如将 x x x分给 s [ i + 1 ] s[i+1] s[i+1]
差距越大能传递的 d d d越大
同时传递后要保证 s [ i ] > = t [ i ] s[i]>=t[i] s[i]>=t[i],当循环到某一个数后,发现他的 s < t s<t s<t,则无解
因为我们从大到小一一帮扶过来
此时的数一定接受了比他大的所有的数多出来的 t [ ] − s [ ] t[]-s[] t[]s[]
全加上也没到要求
后面的数也帮不了它,因为 s [ 该 数 ] > = s [ 后 面 的 数 ] s[该数]>=s[后面的数] s[]>=s[],无法进行转移

code

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 300005
#define int long long
struct node {
	int val, id;
}s[MAXN], t[MAXN];
struct noded {
	int l, r, val;
	noded( int L, int R, int V ) {
		l = L, r = R, val = V;
	}
};
vector < noded > ans;
int n, sums, sumt;

bool cmp( node x, node y ) {
	return x.val > y.val;
}

signed main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &s[i].val );
		s[i].id = i, sums += s[i].val;
	}
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &t[i].val );
		t[i].id = i, sumt += t[i].val;
	}
	if( sums != sumt ) return ! printf( "NO" );
	sort( s + 1, s + n + 1, cmp );
	sort( t + 1, t + n + 1, cmp );
	int last = 1;
	for( int i = 1;i <= n;i ++ ) {
		if( s[i].val < t[i].val ) return ! printf( "NO" );
		while( s[i].val > t[i].val ) {
			while( t[last].val <= s[last].val ) last ++;
			int temp;
			temp = min( t[last].val - s[last].val, s[i].val - t[i].val );
			temp = min( temp, ( s[i].val - s[last].val ) >> 1 );
			s[i].val -= temp, s[last].val += temp;
			ans.push_back( noded( s[last].id, s[i].id, temp ) );
		}
	}
	printf( "YES\n%d\n", ans.size() );
	for( int i = 0;i < ans.size();i ++ )
		printf( "%lld %lld %lld\n", ans[i].l, ans[i].r, ans[i].val );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值