2019-10-29 CSP-S模拟测试

D1

题目

T1

在这里插入图片描述
一句话题意:解一个不定方程 a x + b y = x ( x ) , m i n { x + y } ax+by=x(x为当前处理的数),取min\{|x|+|y|\} ,也就是说,先用 e x g c d exgcd 求出一组解 ( x , y ) (x’,y’) ,那么解集是 ( x + k b , y k a ) (x’+kb,y’-ka) 。显然x只会取最小的正值或最大的负值。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,a,b,k,ans,x,y;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
}
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}
int exgcd(int a,int b,int m,int &x,int &y){
	if(!b)	x=m/a,y=0;
	else{
		exgcd(b,a%b,m,x,y);
		swap(x,y);
		y-=(a/b)*x;
	}
}
signed main(){
	n=read(),a=read(),b=read();
	int j;
	k=gcd(a,b);
	a/=k,b/=k;
	if(a<b)	swap(a,b);//使y的变化趋势比x大 
	for(int i=1;i<=n;i++){
		j=read();
		if(j%k){printf("-1");return 0;}
		exgcd(a,b,j/k,x,y);
		int tmp=(-y)/a+1;//ceil(y/a)
		if(y<0){
			x-=b*tmp;y+=a*tmp;//使x,y最接近0
		}
		x+=b*(y/a);y-=a*(y/a);//避免x或者y加过头
		ans+=min(abs(x)+abs(y),abs(x+b)+abs(y-a));
	}
	printf("%lld",ans);
	return 0;
}

注意
a x + b y = g c d ( a , b ) + k l c m ( a , b ) a ( x + k b g c d ( a , b ) ) + b ( y k a g c d ( a , b ) ) = g c d ( a , b ) ax+by=gcd(a,b)+k*lcm(a,b)\\ a(x+k\frac{b}{gcd(a,b)})+b(y-k\frac{a}{gcd(a,b)})=gcd(a,b)
e x g c d ( x , y ) exgcd(x,y) 时返回值一定要 m / a m/a , / a /a 不能省!,不然有些解就求不出来了
总结:题意很好提炼,重点在于怎么找出 m i n { x + y } min\{|x|+|y|\} ,我们需要先求出一个特解之后考虑怎么让 x , y x,y 逼近 0 0 ,显然通解是 x = x 0 + k b , y = y 0 + k a x=x0+kb,y=y0+ka ,我们只需找出 k k 即可。(PS:为了避免+过头,需要 x + = b ( y / a ) ; y = a ( y / a ) x+=b*(y/a);y-=a*(y/a) ,反正如果 y y 没超过 a a + o r +or- 都不产生影响)

T2

在这里插入图片描述

pair(from solution):
考虑两个数对(ai,bi)和(aj,bj),如果ai<bj并且bi>aj,那么我们希望i排在
j前面。对于相反的情况,我们希望j排在i前面。其余两种情况i和j以任意
顺序排列都是相同的。显然按a+b从小到大排列就可以满足所有的需求。
确定顺序以后,我们设f[i][j]表示前i个数中,已选的数的max(a)=j,最
多选了多少个数。转移时只需要更新选了第i个数的情况,分别考虑j<=min(ai,bi)和ai<j<=bi,用线段树维护即可。
时间复杂度O(nlogn)

总结:这道题重点在于邻项交换的正确性,注意排序需满足严格弱序。然后用线段树维护 我线段树好菜啊TAT有没有大佬愿意拯救一下我
代码来自FSYorz

#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 1e5 + 5;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt * 10 + (ch - '0'), ch = getchar();
	return cnt * f;
}
int n, c[N << 1], siz, id[N];
struct node{ int a, b, w; bool operator < (cs node &A) cs{ return a + b < A.a + A.b; } }x[N];
typedef long long ll;
struct segmentree{	
	ll mx[N << 3], tg[N << 3];
	#define mid ((l+r)>>1)
	void pushup(int x){ mx[x] = max(mx[x<<1], mx[x<<1|1]); }
	void pushnow(int x, ll v){ tg[x] += v; mx[x] += v; }
	void pushdown(int x){ if(tg[x]) pushnow(x<<1, tg[x]), pushnow(x<<1|1, tg[x]), tg[x] = 0; } 
	void modify(int x, int l, int r, int p, ll v){
		if(l == r){ mx[x] = max(mx[x], v); return; } 
		pushdown(x);
		if(p <= mid) modify(x<<1, l, mid, p, v);
		else modify(x<<1|1, mid+1, r, p, v);
		pushup(x); 
	}
	void add(int x, int l, int r, int L, int R, ll v){
		if(L<=l && r<=R){ pushnow(x, v); return; }
		pushdown(x);
		if(L<=mid) add(x<<1, l, mid, L, R, v);
		if(R>mid) add(x<<1|1, mid+1, r, L, R, v);
		pushup(x);
	}
	ll query(int x, int l, int r, int L, int R){
		if(L<=l && r<=R) return mx[x]; pushdown(x); ll ans = 0;
		if(L<=mid) ans = max(ans, query(x<<1, l, mid, L, R));
		if(R>mid) ans = max(ans, query(x<<1|1, mid+1, r, L, R));
		return ans;
	}
}seg;
int main(){
	n = read();
	for(int i = 1; i <= n; i++){
		x[i].a = c[++siz] = read(), x[i].b = c[++siz] = read(), x[i].w = read();
	} sort(c + 1, c + siz + 1); siz = unique(c + 1, c + siz + 1) - (c + 1);
	for(int i = 1; i <= n; i++){
		x[i].a = lower_bound(c + 1, c + siz + 1, x[i].a) - c;
		x[i].b = lower_bound(c + 1, c + siz + 1, x[i].b) - c;
	} sort(x + 1, x + n + 1);
	for(int i = 1; i <= n; i++){
		if(x[i].a >= x[i].b){
			ll f = seg.query(1, 1, siz, 1, x[i].b);
			seg.modify(1, 1, siz, x[i].a, f + x[i].w); // 单点修改 max 
		} 
		else{
			ll f = seg.query(1, 1, siz, 1, x[i].a);
			seg.modify(1, 1, siz, x[i].a, f + x[i].w);
			seg.add(1, 1, siz, x[i].a + 1, x[i].b, x[i].w);
		} 
	} cout << seg.mx[1]; return 0;
}

T3在这里插入图片描述

distance:
以所有特殊点为起点跑多源最短路,并且记录每个点是由哪个源点拓展的。然后枚举所有边,如果边的两端是由不同源点拓展的,就更新这两个点的答案。不难证明,对于源点 i i ,由 i i 拓展的点 j j 以及与 j j 相邻且不由 i i 拓展的点 k k ,如果 i i 的最优路径从 j j 走到了 k k ,那么走到拓展 k k 的源点是最优的。因此这个做法是正确的。
时间复杂度 O ( ( n + m ) l o g n ) O((n+m)logn)

总结:考试时想到了 B F S BFS ,但是觉得有点扯 就没有写,以后要好好分析每个思路的可行性啊。
思路

  1. 初始化每个特殊点的距离为 0 0 和前驱节点为自己
  2. 把每个点放进队列里
  3. 以每个点为源点跑最短路
  4. 更新前驱节点到每个点的最短距离
  5. 输出答案
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int N=200010;
int n,m,p,u[N],pre[N];
long long dis[N],ans[N];
vector<int> edge[N],val[N];
set<pair<long long,int> >f;
pair<long long,int> w;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
}
int main(){
	int i,j,k,l;
	n=read(),m=read(),p=read();
	for(int i=1;i<=p;i++)	u[i]=read();
	for(int i=1;i<=m;i++){
		j=read(),k=read(),l=read();
		edge[j].push_back(k);
		val[j].push_back(l);
		edge[k].push_back(j);
		val[k].push_back(l);
	}
	for(int i=1;i<=n;i++)	dis[i]=ans[i]=1e18;	
	for(int i=1;i<=p;i++)	dis[u[i]]=0,pre[u[i]]=u[i];
	for(int i=1;i<=n;i++)	f.insert(make_pair(dis[i],i));
	while(!f.empty()){
		w=*f.begin();f.erase(w);i=w.second;
		for(j=0;j<edge[i].size();j++){
			k=edge[i][j];l=val[i][j];
			if(dis[k]>dis[i]+l){
				f.erase(make_pair(dis[k],k)); dis[k]=dis[i]+l;pre[k]=pre[i];f.insert(make_pair(dis[k],k));
			}
		}
	}
	for(i=1;i<=n;i++)
		for(j=0;j<edge[i].size();j++)
			if(pre[i]!=pre[edge[i][j]])
				ans[pre[i]]=min(ans[pre[i]],dis[i]+val[i][j]+dis[edge[i][j]]);
	for(int i=1;i<=p;i++)
		printf("%lld ",ans[u[i]]);
	return 0;	
}

不知道为什么我跑了 8081 m s 8081ms ,加了 O 2 O2 也不快,难道我自带大常数???

发布了37 篇原创文章 · 获赞 11 · 访问量 1937

猜你喜欢

转载自blog.csdn.net/weixin_42750325/article/details/102827001