[Codeforces Round 857 (Div. 1)][Codeforces 1801A~1801G(部分,已更新A~D、F)]

FST哩,好似!本来能+80的,现在只加了30,相当于掉了50分捏

1801A - The Very Beautiful Blanket

题目大意:要求构造一个 \(n\times m\) 的矩阵 \(B\),使得对任意一个 \(4\times 4\) 的子矩阵 \(A\) 均满足 \(A_{13} \oplus A_{14} \oplus A_{23} \oplus A_{24} = A_{31} \oplus A_{32} \oplus A_{41} \oplus A_{42}\)\(A_{13} \oplus A_{14} \oplus A_{23} \oplus A_{24} = A_{31} \oplus A_{32} \oplus A_{41} \oplus A_{42}\)

直接令 \(A_{ij}=256\times i+j\) 即可,原理是这样构造可以让任意一个 \(2\times 2\) 的子矩阵满足异或和为零,因为在二进制下对应权值最低的八位代表列号,更高位代表行号,各部分都是能各自消掉的。

#include<bits/stdc++.h>
using namespace std;
#define N 300
int T,n,m,a[N][N];
int main()
{
	for(int i=0;i<256*256;i++){
		int x=i/256,y=i%256;
		a[x][y]=i;
	}
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		printf("%d\n",n*m);
		for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			printf("%d%c",a[i][j],j<m-1?' ':'\n');
	}
}

1801B - Buying gifts

题目大意:有 \(n\) 个物品,每个物品有两个权值 \(a_i,b_i\)分别表示该物品给 \(A,B\) 两个人的权值。要求把这 \(n\) 个物品分配给两人使得 \(|maxA-maxB|\) 尽可能小。\(maxA,maxB\) 分别表示两人分配到物品的最大权值,每人至少要被分配一个物品。

这题我 FST 了,原因是当所有物品一样的时候我全买给了 \(A\),好似!

这种题的做法有一个惯用套路就是固定其中一维然后求解。假设我们钦定了 \(maxA\) 的值,那么可以发现所有 \(a_i>maxA\) 的物品都必须给 \(B\),而这时其它物品怎么选都不会影响 \(maxA\) 的值,那么在这些物品中选一个 \(b_i\)\(maxA\) 最接近的给 \(B\) 即可。

于是就得到了一种做法,先把物品按照 \(a_i\) 排序,从小到大枚举钦定哪一个物品是必须给 \(A\) 的,这时后面的所有物品就都得给 \(B\),可以预处理后缀 \(\max\) 求出这一部分对 \(maxB\) 的贡献。而对于前面选离 \(maxA\) 最近的值,利用 \(\mathrm{multiset}\) 中的 \(\mathrm{lower\_bound}\) 乱搞即可。

注意由于 \(a_i\) 的值可能相同,所以需要对相同的 \(a_i\) 分批处理,具体实现见代码。

\(\mathrm{Find}\) 函数中的 \(o\) 就是特判之前 FST 的情况用的。

#include<bits/stdc++.h>
using namespace std;
#define N 500010
int n,f[N],ans;
multiset<int>s;
struct rua
{
	int x,y;
	void read(){scanf("%d%d",&x,&y);}
	bool operator <(const rua &t)const{return x<t.x;}
}a[N];
int Find(int x,int y,int o)
{
	int res=o?1e9:abs(x-y);
	auto it=s.lower_bound(x);
	if(it!=s.end()){
		int z=max(y,*it);
		res=min(res,abs(x-z));
	}
	if(it!=s.begin()){
		it--;
		int z=max(y,*it);
		res=min(res,abs(x-z));
	}
	return res;
}
void init()
{
	ans=2e9;
	s.clear();
	scanf("%d",&n);
	for(int i=1;i<=n;i++)a[i].read();
	sort(a+1,a+n+1);
	f[n+1]=0;
	for(int i=n;i>=1;i--)f[i]=max(a[i].y,f[i+1]);
	for(int i=1,j;i<=n;i=j+1){
		j=i;
		while(j<=n && a[j].x==a[i].x)j++;j--;
		int x=a[i].x;
		int y=f[j+1];
		for(int k=i;k<=j;k++)s.insert(a[k].y);
		for(int k=i;k<=j;k++){
			s.erase(s.find(a[k].y));
			ans=min(ans,Find(x,y,j==n));
			s.insert(a[k].y);
		}
	}
	printf("%d\n",ans);
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)init();
}

1801C - Music Festival

题目大意:定义一个序列 \(A\) 的价值为满足 \(A_i=\max_{j=1}^{i}A_j\)\(i\) 的个数。给定 \(n\) 个序列,要求以任意方式将他们拼接起来,使得得到的序列价值最大。

首先可以得到,对于每个序列,只有满足对应性质的那些元素才有可能对答案产生贡献,所以首先可以在输入的时候就对序列进行预处理,留下一个严格递增的序列。

考虑 \(\mathrm{DP}\),令 \(f_i\) 表示以数字 \(i\) 为结尾的序列的最大价值,那么可以得到如果把某个序列 \(A\) 接在尾部,对应的方案只能更新到 \(f_{maxA}\) 上。于是考虑把序列按照最后一个元素大小进行排序。

\(\mathrm{DP}\) 的过程中,考虑对每个序列枚举该序列是从第几个位置开始产生贡献的。假设当前在序列 \(A\) 中枚举到的位置为 \(i\)(这题本人实现下标是从 \(0\) 开始),序列处理后的长度为 \(k\),最大值为 \(m\),那么找到最大的 \(j\) 使得 \(j<a_i\)\(f_j\) 有值,此时 \(f_j\) 就会对 \(f_m\) 产生 \(f_j+k-i\) 的贡献,实时更新答案即可。\(j\) 的查找可以通过记录所有当前 \(f\) 有值的位置,\(\mathrm{lower\_bound}\) 一下即可。最后不要忘了多测清空。

#include<bits/stdc++.h>
using namespace std;
#define N 200010
int T,n,f[N];
struct rua
{
	int m,k;
	vector<int>d;
	void init(){m=k=0;d.clear();}
	void read(){
		d.clear();
		scanf("%d",&k);
		for(int i=1;i<=k;i++){
			int x;
			scanf("%d",&x);
			if(x<=m)continue;
			d.push_back(x);
			m=x;
		}
		k=d.size();
	}
	bool operator <(const rua &t)const{return m<t.m;}
}a[N];
vector<int>v;
void init()
{
	int ans=0;
	v.clear();
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		a[i].init();
		a[i].read();
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++){
		for(int j=0;j<a[i].k;j++){
			int x=a[i].d[j];
			int k=lower_bound(v.begin(),v.end(),x)-v.begin()-1;
			f[a[i].m]=max(f[a[i].m],a[i].k-j);
			if(k>=0)f[a[i].m]=max(f[a[i].m],f[v[k]]+a[i].k-j);
			ans=max(ans,f[a[i].m]);
		}
		v.push_back(a[i].m);
	}
	for(auto i:v)f[i]=0;
	printf("%d\n",ans);
}
int main()
{
	scanf("%d",&T);
	while(T--)init();
}

1801D - The way home

题目大意:一个 \(n\) 个点 \(m\) 条边的有向图,每条边有对应花费。某个憨憨演唱家现在身上只有 \(p\) 元钱,他可以选择在某个点 \(i\) 开演唱会以得到 \(s_i\) 的收入。问要从 \(1\) 走到 \(n\) 所需要举办的演唱会次数最小是多少。

首先考虑如果回家的路径确定了,唱歌的决策是怎样的。可以发现最后肯定是在某个点 \(x\) 死命唱歌直到恰好攒够回家的钱,然后直接走最小花费的路径跑路。接着再倒着往回推,在这之前他肯定也是在另一个点恰好攒够钱后再动身来到 \(x\)。所以可以得到如果已经选定了先后在哪些位置开演唱会,那么其唱歌决策是确定的,即每次在当前位置一直举办演唱会直到攒够前往下一个位置的钱。

于是跑 \(n\) 遍单源最短路,对所有 \((i,j)\) 预处理出从点 \(i\) 走到点 \(j\) 的最小花费 \(dis_{i,j}\)。再使用类似最短路的过程贪心选取下个演唱会的地点。那么在这一部分的最短路中,有两个参考值,一个是 \(cost_i\) 表示到达 \(i\) 这个点需要举办演唱会的最小次数,另一个是 \(res_i\) 表示在次数最小的前提下,所剩余钱数的最大值。那么根据这两个信息跑最短路即可,每次到点 \(x\) 时,枚举下一个唱歌的位置 \(i\),根据当前的 \(res_x\)\(dis_{x,i}\) 的差值以及在 \(x\) 举办演唱会的收入 \(s_x\) 可以得到需要举办演唱会的次数 \(k\),直接转移即可。最后答案就是 \(cost_n\)

#include<bits/stdc++.h>
using namespace std;
#define N 810
#define LL long long
int T,n,m,p,b[N],vis[N];
LL dis[N],cost[N],res[N];
vector<pair<int,int>>d[N];
struct rua
{
	int x;
	LL cost,res;
	bool operator <(const rua &t)const{
		if(cost!=t.cost)return cost<t.cost;
		return res>t.res;
	}
};
set<rua>s;
void Dij(int x)
{
	set<pair<LL,int>>q;
	for(int i=0;i<=n;i++)dis[i]=1e18;
	dis[x]=0;
	for(int i=1;i<=n;i++)q.insert(make_pair(dis[i],i));
	while(!q.empty()){
		auto PI=*q.begin();
		q.erase(q.begin());
		int x=PI.second;
		for(auto pi:d[x]){
			LL nd=dis[x]+pi.second;
			int y=pi.first;
			if(nd<dis[y]){
				q.erase(make_pair(dis[y],y));
				dis[y]=nd;
				q.insert(make_pair(dis[y],y));
			}
		}
	}
}
LL ceil(LL x,LL y)
{
	if(x<0)return 0;
	if(x%y==0)return x/y;
	return x/y+1;
}
void init()
{
	s.clear();
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=n;i++){
		d[i].clear();
		scanf("%d",&b[i]);
		vis[i]=0;
		cost[i]=1e18;
		res[i]=0;
	}
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		d[u].push_back(make_pair(v,w));
	}
	Dij(1);
	if(dis[n]==dis[0]){
		printf("-1\n");
		return;
	}
	cost[1]=0,res[1]=p;
	s.insert((rua){1,cost[1],res[1]});
	while(!s.empty()){
		auto X=*s.begin();
		s.erase(s.begin());
		int x=X.x;
		vis[x]=1;
		Dij(x);
		for(int i=1;i<=n;i++)if(!vis[i]){
			LL k=ceil(dis[i]-res[x],b[x]);
			rua tmp={i,cost[x]+k,res[x]+k*b[x]-dis[i]};
			rua I={i,cost[i],res[i]};
			if(tmp<I){
				s.erase(I);
				cost[i]=tmp.cost;
				res[i]=tmp.res;
				s.insert(tmp);
			}
		}
	}
	printf("%lld\n",cost[n]);
}
int main()
{
	scanf("%d",&T);
	while(T--)init();
}

赛场上为了省空间写了个类似于最短路套最短路的玩意orz

1801E - Gasoline prices

不会捏,留坑待填

1801F - Another n-dimensional chocolate bar

题目大意:给定一个长度为 \(n\) 的数组 \(a\) 和正整数 \(k\),要求构造一个数组 \(b\) 满足 \(1\le b_i \le a_i\)\(\prod_{i=1}^{n}b_i \ge k\),使得 \(\lfloor \frac{a_1}{b_1} \rfloor \dotsm \lfloor \frac{a_n}{b_n} \rfloor \cdot \frac{1}{a_1 a_2 \cdots a_n} \cdot k\) 尽可能大,输出这个最大值。

由于 \(a_i\)\(k\) 已经给出,所以就相当于要在满足条件的前提下最大化 \(\prod_{i=1}^{n}\lfloor \frac{a_i}{b_i} \rfloor\)。考虑一个朴素的 \(\mathrm{DP}\)\(f_{i,j}\) 表示当前到第 \(i\) 个位置,对应 \(b_i\) 的前缀乘积为 \(j\) 时,\(\lfloor \frac{a_i}{b_i} \rfloor\) 乘积的最大值。这样直接做复杂度爆炸。

接着考虑优化,因为要求的是 \(\prod b_i\ge k\),所以不妨把 \(\mathrm{DP}\) 改成令 \(f_{i,j}\) 表示到第 \(i\) 个位置,要求剩下 \(b_i\) 乘积 \(>j\) 的答案,这样转移的时候 \(j\) 的值大概就能减少的很快,每次枚举 \(b_i\) 的值就能直接从 \(j\) 转移到 \(\lfloor\frac{j}{b_i}\rfloor\) 上。从 \(f_{1,k-1}\) 一路转移到 \(f_{n,0}\) 就是答案。

这时发现在转移过程中,实际上所有的 \(j\) 必然等于 \(\lfloor\frac{k-1}{x}\rfloor\)\(x\) 为某个正整数),于是考虑数论分块预处理出所有可能的 \(j\),在这些位置间转移即可,显然这样的 \(j\) 只有 \(O(\sqrt k)\) 个。而每次 \(i,j\) 能转移到的位置数则是 \(O(\sqrt j)\) 个的,所以直接转移的总复杂度与杜教筛类似,为 \(O(n\cdot k^{\frac{3}{4}})\)

#include<bits/stdc++.h>
using namespace std;
#define N 110
#define ld long double
int n,m,k,x,a[N],b[N*N],id[1<<24];
ld f[N][N*N],ans=1;
int Fl(int x,int y){return x/y;}
int main()
{
	scanf("%d%d",&m,&k);
	ans*=k,k--;int kk=k;
	for(int i=1;i<=m;i++){
		scanf("%d",&x);
		if(x>1)a[++n]=x;
		ans/=x;
		kk/=x;
	}
	if(kk)return printf("0\n"),0;
	m=0;
	for(int l=1,r;l<=k;l=r+1){
		r=k/(k/l);
		b[++m]=r;
		id[r]=m;
	}
	f[0][m]=1;
	for(int i=1;i<=n;i++){
		f[i][0]=f[i-1][0]*a[i];
		for(int j=1;j<=m;j++)if(f[i-1][j]){
			x=b[j];
			for(int l=1,r;l<=a[i];l=r+1){
				int nxt=x/l;
				if(nxt)r=x/nxt;else r=a[i];
				f[i][id[nxt]]=max(f[i][id[nxt]],f[i-1][j]*Fl(a[i],l));
			}
		}
	}
	printf("%.10Lf\n",f[n][0]*ans);
}
posted @ 2023-03-10 09:29  DeaphetS  阅读(263)  评论(1编辑  收藏  举报