Codeforces Round #576 (Div. 2) (C+D+E+F)

C. MP3

题意:给定一个数列,可以选择一个区间[l,r],设数列在区间内有k个不相同的元素,那么k必须满足 n*[log2k] <= 8*I,t=[log2k]表示满足k<=2 ^ t 最大的整数k。
问最多可以使数列多少个数在你选的这个区间内,输出剩余的元素个数。

分析:
这题题意首先有点难懂,抽象成上面这个样子之后就差不多了。
其次这题会卡精度,不能直接用log2()函数,
不过我们可以先打一个2的幂表,然后二分查找求出k 。

然后把数列离散化,记录每个值的个数,再按大小排序。
问题就等价为在离散化后的数组排序后,找出一个长度为k的子区间段,使得值的个数和最大。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 4e5+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 34359738370;
const int INF = 1e9+7;
map<int,int> mp;//离散化
int n,m,a[maxn];
int cnt=0;
int siz[maxn];
int sum=0;
int pre[maxn];
ll pow2[33];
int main()
{
	pow2[0]=1;
	for(int i=1;i<=32;i++) pow2[i]=pow2[i-1]*2;
    int t;t=1;
    while(t--)
    {
		scanf("%d %d",&n,&m);
		m*=8;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",a+i);
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++)
		{
			if(!mp[a[i]])
			{
				mp[a[i]]=++cnt;
				siz[mp[a[i]]]=1;
			}
			else 
			{
				siz[mp[a[i]]]++;
			}
		}
		for(int i=1;i<=cnt;i++) pre[i]=pre[i-1]+siz[i];
		int k=cnt,x;
		for( ;k>=2;k--)
		{
			x=lower_bound(pow2,pow2+32,k)-pow2;
			if(1ll*n*x <= 1ll*m) 
			{
				break;
			}
		}
		//如果k==1  那么说明只能有一个distinct数
		//从siz的定义域中找出一个长度为k的区间 使得和最大
		//用前缀和跑一次就行
		for(int i=1;i<=cnt-k+1;i++)
		{
			sum=max(sum,pre[i+k-1]-pre[i-1]);
		}
		cout<<n-sum;
    }
    return 0;
}

D. Welfare State

题意:给定数列,操作1:单点修改 ,操作2:每个小于x的元素改成x
问q次操作后,这个数列为?

分析:刚开始脑子一热以为是线段树维护区间max、min+剪枝,打了一发发现tle32了,冷静分析发现这题不需要在线询问,只有最终的离线询问,所以不需要线段树,(而且玄学剪枝不可信)。

我们可以记录每个位置最后一次单点修改时的时间(也就是第几次操作),然后维护操作2的后缀最大值。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 4e5+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 34359738370;
const int INF = 1e9+7;
//给定数列 q次操作 单点修改或者把区间每个小于x的数变成x
//q次之后 询问数组每个元素
//每个位置 记录最新单点修改的时间戳 
//整体修改 维护后缀最大值
int n,q;
int a[maxn];
int sufmax[maxn];
int upclock[maxn];
int change[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",a+i);
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		int f,x,v;scanf("%d",&f);
		if(f==1) 
		{
			scanf("%d %d",&x,&v);
			upclock[x]=i;
			a[x]=v;
		}
		else 
		{
			scanf("%d",&x);
			change[i]=x;
		}
	}
	for(int i=q;i>=1;i--)
	{
		sufmax[i]=max(sufmax[i+1],change[i]);
	}
	for(int i=1;i<=n;i++)
	{
		printf("%d ",max(a[i],sufmax[upclock[i]+1]));
	}
	return 0;
}

E. Matching vs Independent Set

题意:给出一张含有3n个点和m条边的图,现要从中找到一个n个点的独立集,或者n条边的匹配。边的匹配定义为任意两条边不存在公共结点。

分析:
每个顶点有且只能对某一条边的匹配作出贡献,所以
我们在输出边的时候就可以记录匹配,如果大于等于n条,那么直接输出匹配即可,如果小于n条,那么就相当于在这张图上,把这些匹配边以及所连的顶点删去,剩余的顶点没有公共边,剩余顶点数为n-2*匹配 ,一定大于n。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 3e5+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 34359738370;
const int INF = 1e9+7;
//给定顶点个数为3n的图 输出n个点的独立集 或者n条边的匹配
bool vis[maxn];
vector<int> e;
int n,m;
int main()
{
    int t;cin>>t;
    while(t--)
    {
        scanf("%d %d",&n,&m);
        e.clear();
        for(int i=1;i<=n*3;i++) vis[i]=0;
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d %d",&u,&v);
            if(!vis[u] && !vis[v]) 
            {
                vis[u]=vis[v]=1;
                e.push_back(i);
            }
        }
        if((int)e.size() >= n) 
        {
            cout<<"Matching"<<'\n';
            e.resize(n);
            for(const int &i:e) printf("%d ",i);
        }
        else 
        {
            cout<<"IndSet"<<'\n';
            for(int i=1,cnt=1;cnt<=n && i<=3*n;i++)
            {
                if(!vis[i]) 
                {
                    cnt++;
                    printf("%d ",i);
                }
            }
        }
        cout<<'\n';
    }
    return 0;
}

F. Rectangle Painting 1

题意:给定n * n的网格 有黑白格子 选定h * w的矩阵染成白色 代价为max(h,w) 问最少花费多少代价 可以使得n * n全是白色格子

分析:从数据来看 n最大为50,而且如果我们把w*h的网格分成2部分,那么整体最优解其实是min(两部分最优解之和,max(w,h)),于是可以考虑用区间dp。

dp[x1][y1][x2][y2] 表示(x1,y1) -> (x2,y2) 所表示矩阵的最小代价
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 3e5+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 34359738370;
const int INF = 1e9+7;
//给定n*n的网格 有黑白格子 选定h*w的矩阵染成白色 代价为max(h,w) 问最少花费多少代价 可以使得n*n全是白色格子
//如果我们把网格分成2部分,那么整体的最优解就是两部分最优解之和
//考虑用区间dp
//dp[x1][y1][x2][y2] 表示(x1,y1) -> (x2,y2) 所表示矩阵的最小代价
char m[55][55];
int dp[55][55][55][55];
int n;
int dfs(int x1,int y1,int x2,int y2)//记忆化搜索
{
    if(dp[x1][y1][x2][y2] != -1) return dp[x1][y1][x2][y2];
    if(x1==x2 && y1==y2) 
    {
        dp[x1][y1][x2][y2]=(m[x1][y1]=='#');
        return dp[x1][y1][x2][y2];
    }
    int ret=max(x2-x1+1,y2-y1+1);
    //分成左右两部分
    for(int i=x1;i<x2;i++)
    {
        ret=min(ret,dfs(x1,y1,i,y2)+dfs(i+1,y1,x2,y2));
    }
    //分成上下两部分
    for(int i=y1;i<y2;i++)
    {
        ret=min(ret,dfs(x1,y1,x2,i)+dfs(x1,i+1,x2,y2));
    }
    dp[x1][y1][x2][y2]=ret;
    return ret;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%s",m[i]+1);
    memset(dp,-1,sizeof dp);
    cout<<dfs(1,1,n,n);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值