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;
}