蓝桥杯系统:蓝桥杯练习A组省赛/决赛【PREV275-241】

PREV275:双向排序

双向排序
题意
给定序列 ( a 1 , a 2 , … , a n ) = ( 1 , 2 , … , n ) (a_1,a_2,\dots,a_n)=(1,2,\dots,n) (a1,a2,,an)=(1,2,,n),即 a i = i a_i = i ai=i。对这个序列进行m次操作。一共两种操作:

  1. op = 0,pos = i:将 a 1 , a 2 , … , a i a_1,a_2,\dots,a_i a1,a2,,ai降序排列;
  2. op = 1,pos = i:将 a i , … , a n − 1 , a n a_i,\dots,a_{n-1},a_n ai,,an1,an升序排列。
    求完成操作后的序列

思路
用(op,pos)来简写表示操作op和pos的选择,用[x,pos]表示数x的位置在pos上。
对于数n的位置,如果存在(0,n)并且该操作之后不再有(1,1),那么一定[n,1]成立。因为n是最大的,(0,n)排序之后n必然在第一位,所有的(1,?)都无法动摇数n,而(1,?)中只有(1,1)能碰到位置1,但是它也不再有,所以[n,1]一定成立。反之,如果(1,1)的出现位置在(0,n)之后,那么[n,n]一定成立。理由同理。如果这两个都没有出现,可以默认初始状态是(1,1)。并且可以发现其他数字在(0,n)(1,1)之前的操作都没有意义,因为这两个操作会重排所有数据。

接下来判断n-1的位置,会发现正常情况下n位置确定后n-1是粘着n的,因为无论升序降序n-1都是和n连着的。那么可以得到n判断结束后的n-1的所在位置。
假设还空着的位置的范围是[zuo,you],由于n位置已经定了,n-1是剩下的数里面最大的值,那么n-1的最终值要么在zuo要么在you。

假设n定完后n-1在zuo,那么(0,?)已经无法影响到n-1的位置了,只需要判断(1,zuo)是否在后续中出现了,如果出现了[n-1,you]必定成立,反之[n-1,zuo]必定成立。定完n-1的位置之后,n-2的初始位置也定了,更新下zuo、you的值。然后同理推完所有数值的位置

//但是很难受了想了很久,正式比赛的时候想这么久还不如暴力多做几道题,泪目了

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
using namespace std;
typedef long long ll;
 
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mod = 1e9+7;
const ll mx = 1e5 + 10;
const ll inf = 1<<30;

ll sit[2][mx];
ll a[mx], n, m;
ll ans[mx], zuo, you;

void dfs(ll x, ll curr, ll mystart){
	if(x == 0) return;
//x是当前的值 curr是当前值的位置 
	if(x == n){//特判 
		mystart = max(sit[0][n], sit[1][1]);
		if(sit[0][n]>sit[1][1]){//降序排序 
			ans[1] = x;
			zuo ++;
			dfs(x-1,2, mystart);
		}
		else{//保持升序 - 包括两者都不出现的时候 
			ans[n] = x;
			you --;
			dfs(x-1,n-1, mystart);
		}
		return;
	}	
	//目前的未固定范围[zuo,you] 
	if(curr == zuo){
		//影响到位置变换的操作是 sit[1][zuo]
		if(sit[1][zuo]>mystart){//就会影响 
			ans[you] = x;
			you --;
			dfs(x-1, you, sit[1][zuo]);
		} 
		else{//不会影响 保持原状 
			ans[zuo] = x;
			zuo ++;
			dfs(x-1, zuo, mystart);
		}
	}
	else{//curr == you
		//影响到位置变换的操作是 sit[0][you]
		if(sit[0][you]>mystart){
			ans[zuo] = x;
			zuo ++;
			dfs(x-1, zuo, sit[0][you]);
		} 
		else{
			ans[you] = x;
			you --;
			dfs(x-1, you, mystart);
		}
	}
}
void solve(){
	scanf("%lld %lld", &n, &m);
	for(ll i = 1; i <= m; i++){
		ll op, pos;
		scanf("%lld %lld", &op, &pos);
		sit[op][pos] = i;
	}
	zuo = 1, you = n;
	dfs(n, n, 0);
	for(ll i = 1; i <= n; i++){
		printf("%lld", ans[i]);
		if(i==n) puts("");
		else printf(" ");
	}
}

int main(){
	solve();
	return 0;
}
PREV270:括号序列

括号序列
题意
给定一个括号序列,要求添加最少的括号数使合法。求有多少种本质不同的添加结果。本质不同指最后完整的括号序列至少有一个位置上的字符不同。
思路
首先,从左至右肯定是先添加完 右括号 再添加 左括号。否则会出现()这样的无意义的添加,是一种浪费,不可能是最少的括号。
所以肯定可以把括号序列划分为两段,左边一段是添加右括号的,右边一段是添加左括号的。(或者只存在一段)。
因此可以把括号和下标值压栈,配对出栈。这样最后栈里留下的字符一定是)))……(((,找到最右边的)和最左边的(,这两个字符就是划分左右区段的标志。
接着,考虑到求左区段方案数和右区段方案数其实是等价的(把右区段翻转然后符号替换)。所以可以只考虑求左区段的方案数求法。最后左右区段的方案数乘起来取模得到答案。

对于()))类似的的左区段(右括号的数量多于左括号),可以从右到左dp。
对于从左至右第i个右括号,dp[j]表示从当前右括号的左邻到括号序列的右末放进去j个左括号的方案数。
同时记录cv,cv表示当前右括号左邻最多能放下几个左括号。

易得
d p [ i ] = d p ′ [ i ] + d p ′ [ i − 1 ] + ⋯ + d p ′ [ i − c v ] dp[i] = dp'[i]+dp'[i-1]+\cdots+dp'[i-cv] dp[i]=dp[i]+dp[i1]++dp[icv]
这样的复杂度是O(n),但是发现式子右边是连续和,可以再记录一个前缀和sum数组,这样复杂度就变成了O(1)。

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 5e3 + 10;
const ll mod = 1e9 + 7;
const ll inf = 1000000000 + 10;

char str[mx];
ll dp[mx], sum[mx];
ll f(char *s, ll len){//[0,len-1] (())))())
	memset(dp, 0, sizeof dp);
	memset(sum, 0, sizeof sum);
	ll cv = 0, need = 0;//最多放几个( 
	rep(i,0,len-1){
		if(s[i]=='(')need--;
		else need++;
	}

	dp[0] = 1;
	rep(i,0,need)sum[i] = 1;
	
	vector<ll>li;
	ll i = len-1;
	while(i>=0){
		if(s[i]==')')cv++;
		else cv--;
		if(i==0 || s[i-1]==')'){
			li.pb(cv);
		}
		i--;
	}
	
	rep(i,0,li.size()-1){
		cv = li[i];
		rep(i,0,cv){
			if(i-cv-1<0) dp[i] = sum[i];
			else dp[i] = sum[i] - sum[i-cv-1];
			dp[i] = (dp[i]%mod+mod)%mod;
		}
		rep(i,cv+1,need)dp[i] = 0;
		rep(i,1,need) {
			sum[i] = sum[i-1]+dp[i];
			sum[i]%=mod;
		}
	}

	return dp[need];
}

char ff(char c){
	return c==')'?'(':')';
}
void solve(){
//	scanf("%s", str);
	ll len = strlen(str);
	stack<pll>sta;
	rep(i,0,len-1){
		if(sta.empty())sta.push(mp(str[i],i));
		else{
			if(sta.top().fi==str[i]){
				sta.push(mp(str[i], i));
			}
			else{
				if(str[i]==')')sta.pop();
				else sta.push(mp(str[i],i));
			}
		}
	}
	if(sta.size()<=1){
		printf("%lld\n", (ll)sta.size());
		return;
	}
	ll you = -1, zuo = len;//[0,you]  [you+1,len-1]
	while(!sta.empty() && sta.top().fi=='('){
		zuo = sta.top().se;
		sta.pop();
	}
	if(!sta.empty())you = sta.top().se;
	ll ans1 = 1, ans2 = 1;
	if(you!=-1){
		ans1 = f(str,you+1);
	}
	if(zuo!=len){
		ll l = zuo, r=len-1;
		while(l<=r){
			swap(str[l],str[r]);
			str[l] = ff(str[l]);
			if(l!=r) str[r] = ff(str[r]);
			l++,r--;
		}
		ans2 = f(str+zuo,(len-1)-(zuo)+1);
	}
	printf("%lld\n", ans1*ans2%mod);
}
int main(){
//	cin.tie(0); 
//	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	while(scanf("%s",str)!=EOF)
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV268:左孩子右兄弟

左孩子右兄弟
题意
把一个多叉树转化为左孩子右兄弟,可以选择任意孩子作为左孩子。使树深度最大。
思路
设dp[i]表示节点i所在子树的最大深度,容易发现,把dp[son]最大的孩子节点放在最下面更优。则转移方程为:
d p [ i ] = m a x { d p [ s o n ] } + s o n S i z e dp[i] = max\{dp[son]\}+sonSize dp[i]=max{dp[son]}+sonSize
这里我用的深度从1开始。所以最后答案要-1。

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 1000000000 + 10;

ll N;
vector<ll>e[mx];
ll dfs(ll rt){
	ll sonSize = e[rt].size();
	if(sonSize ==0LL)return 1LL;
	ll maxv = 0;
	for(ll i =0; i< sonSize;i++){
		ll son = e[rt][i];
		maxv = max(maxv, dfs(son));
	}
	return maxv + sonSize;
}
void solve(){
	scanf("%lld", &N);
	rep(i,2,N){
		ll fa;
		scanf("%lld", &fa);
		e[fa].pb(i);
	}
	ll ans = dfs(1);
	printf("%lld\n", ans-1LL);
} 


int main(){
//	cin.tie(0); 
//	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV266:异或数列

异或数列
题意
给定长度为n的数列x。Alice和Bob初始值为0,两个人都可以做以下两个操作之一:

  1. Alice的值异或上 X i X_i Xi
  2. Bob的值异或上 X i X_i Xi
    i是自己选定的。但是每个i只能选一次。最后拥有较大数的一方赢,两者相等平局。Alice先手。
    先手赢1,平局0,输-1。

思路
转化为二进制值进行考虑。记录2的i次幂的数量,记为num[i]。
显然,当num[i]为偶数的时候它对于结果没有影响,因为无论这个值给谁,要么偶数次异或为0,要么两者都奇数次异或值相同。
而num[i]为奇数的时候,它一定带来Alice和Bob直接的差距。并且num[i]为奇数的最大的i一定决定了结果,因为其他的小于i的2的次幂的差距之和最大也不可能超过i。
接着简单考虑奇数次个2的次幂的影响。假设次幂为1。
1个1的时候先手赢,3个1的时候先手也赢(先手加给Bob,使Bob进行偶数次异或),5个1的时候先手也赢……
实际上,1一个1的时候确保了先手有异或后手没有,所以赢。
而其他情况,是确保了最后一步由先手来做。那么先手可以根据情况加给自己或者对方,使得对方小于自己。
再来考虑除了奇数次个2的次幂之外存在其他数的情况,由上面可以知道,最后一个处理奇数次幂值的人决定了胜负。所以如果总数n为奇数,先手决定胜负。(特判num[i]为1的情况,因为先手可以确保直接抢占。)

综上,如果num[i]为1,先手胜。如果num[i]全为偶数,平局。如果num[i]奇数且n为奇数先手胜,反之先手败。

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 1000000000 + 10;

ll T, x[mx];
ll num[30];
void solve(){
	scanf("%lld", &T);
	rep(t, 1, T){
		memset(num, 0, sizeof num);
		ll n;
		scanf("%lld", &n);
		rep(i,1,n){
			scanf("%lld", &x[i]);
			rep(j,0,20){
				if(x[i]&1) num[j]++;
				x[i]>>=1;
			}
		}
		ll win = 0;
		irep(i,20,0){
			if(num[i]&1){		
				if(num[i]==1) win = 1;
				else{
					if(n&1) win = 1;
					else win = -1;
				}
				break;
			}
		}
		printf("%lld\n", win);

	}
}


int main(){
//	cin.tie(0); 
//	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV262:砝码称重

砝码称重
题意
N个砝码,第i个砝码重wi。求一共可以称多少种重量。
思路
d p [ i ] [ j ] dp[i][j] dp[i][j]表示前i个砝码是否能构成状态j,状态j表示左边的重量减去右边的重量。由于可能是负数,可以用 d p [ i ] [ b a s e + j ] dp[i][base+j] dp[i][base+j]表示 d p [ i ] [ j ] dp[i][j] dp[i][j]

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
//typedef long long ll;
typedef int ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 2e5 + 10;
const ll base = 1e5 + 5;
const ll total = 1e5;
ll N, w[110];
bool dp[110][mx];

void solve(){
	cin>>N;
	rep(i,1,N)cin>>w[i];
	dp[0][base] = true;
	
	rep(i,1,N){
		rep(j,base - total, base + total){
			dp[i][j] = dp[i-1][j];
			ll bef1 = j - w[i], bef2 = j + w[i];
			if(bef1 >= base-total && bef1 <= base + total)
				dp[i][j] |= dp[i-1][bef1];
			if(bef2 >= base-total && bef2 <= base + total)
				dp[i][j] |= dp[i-1][bef2];
		}
	}
	ll ans = 0;
	rep(j, base + 1, base+total) ans += dp[N][j];
	cout<<ans<<endl;
	
}

int main(){
	cin.tie(0); 
	ios::sync_with_stdio(0);
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV258:重复字符串

重复字符串
题意
如果一个字符串S可以由某个字符串重复K次得到,则S为K次重复字符串。给定字符串S,求至少修改几个字符可以使其变为K次字符串。
思路
字符串S变为K次字符串,就是表示变为K个相同的子串。每个子串里有Y=|S|/|K|个字符。那么可以把题目拆解为Y个长为K的字符数组,求当前数组内字符全部相同的最小修改数,然后求和Y个数组的修改总值。
按照题意,当S不为K的倍数的时候应该返回-1。但是这么做始终只有90分。后来我发现(阴差阳错?)不返回-1然后按照普通求数组修改数能拿到100分。但我不知道为什么而且觉得很没有道理。。感觉是碰巧凑到了答案。

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;

ll K, nn[30]; 
char s[mx], t[mx];

ll num(char*t, ll gs){
	ll ans = 0;
	memset(nn, 0, sizeof nn);
	rep(i,0,gs-1){
		nn[t[i]-'a']++;
		ans = max(ans, nn[t[i]-'a']);		
	}
	return gs-ans;
}

void solve(){
	scanf("%lld", &K);
	scanf("%s", s);

	ll len = strlen(s);
	if(K>len){
		printf("-1\n");
		return;
	}
	if(len == K){
		printf("%lld\n", num(s, len));
		return;
	}
	ll ans = 0, group = len/K;
	rep(lp,0,group-1){
		ll tot = 0;
		for(ll i = lp, j = 0; i<len; i+=group, j++){
			t[j] = s[i];
		}
		ans += num(t, K);
	}
	printf("%lld\n", ans);
	
}

int main(){
//	cin.tie(0); 
//	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV256:天干地支

天干地支
题意
模拟天干地支变化
思路
模拟。但是wa了两次。。。还是要细心稳妥(喷血)

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
string s1[20], s2[20];
void init1(){
	s1[0] = "jia";
	s1[1] = "yi";
	s1[2] = "bing";
	s1[3] = "ding";
	s1[4] = "wu";
	s1[5] = "ji";
	s1[6] = "geng";
	s1[7] = "xin";
	s1[8] = "ren";
	s1[9] = "gui";
}

void init2(){
	s2[0] = "zi";
	s2[1] = "chou";
	s2[2] = "yin";
	s2[3] = "mao";
	s2[4] = "chen";
	
	s2[5] = "si";
	s2[6] = "wu";
	s2[7] = "wei";
	s2[8] = "shen";
	s2[9] = "you";
	
	s2[10] = "xu";
	s2[11] = "hai";
}

ll cy =0, c1 = 6, c2 = 8;

void add(){
	c1++;
	if(c1 == 10)c1=0;
	c2++;
	if(c2 == 12)c2=0;
}
void solve(){
	init1();
	init2();
	ll year;
	cin>>year;
	
	while(cy!=year){
		add();
		cy++;
	}
	cout<<s1[c1]<<s2[c2]<<endl;
}

int main(){
	cin.tie(0); 
	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV248:补给

补给
题意
改进后的TSP问题。每个点经过至少一次,但是可以多次。
思路
先floyd求出两点间的最小合法距离(大于D),然后TSP。

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 20 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
const ll mx2 = 2e6 + 10;
double d[mx][mx], D;
double x[mx], y[mx];
double dp[mx][mx2];

bool is0(double x){
	return abs(x-0.0)<eps;
}

ll n;
double pf(double x){return x*x;}
double dis(ll i, ll j){
	return sqrt(pf(x[i]-x[j])+pf(y[i]-y[j]));
}

void solve(){
	scanf("%lld %lf", &n, &D);
	rep(i,0,n-1)scanf("%lf %lf", &x[i], &y[i]);
	rep(i,0,n-1){
		rep(j,0,n-1){
			d[i][j] = dis(i,j);
			if(d[i][j]>D)d[i][j] = -1;
		}
	}
	rep(k,0,n-1){
		rep(i,0,n-1){
			rep(j,0,n-1){
				if(d[i][k]<0 || d[k][j]<0)continue;
				double nv = d[i][k]+d[k][j];
				if(d[i][j]<0)d[i][j] = nv;
				d[i][j] = min(d[i][j], nv);
			}
		}
	}
	
//	rep(i,0,n-1){
//		rep(j,0,n-1)printf("%.2lf ", d[i][j]);
//		puts("");
//	}
	
	rep(V,1,(1<<n)-1){
		rep(j,0,n-1){
			bool has = (V>>j)&1;
			if(!has)continue;
			ll resV = V - (1<<j);
			if(resV == 0){
				dp[j][V] = d[j][0];
				continue;
			}
			rep(k,0,n-1){
				bool hasK = (V>>k)&1;
				if(!hasK || k == j)continue;
				double nv = d[j][k]+dp[k][resV];
				if(is0(dp[j][V])){
					dp[j][V] = nv;
				}
				dp[j][V] = min(dp[j][V], nv);
			}
		}
	}
	printf("%.2lf\n", dp[0][(1<<n)-1]);
}

int main(){
	cin.tie(0); 
	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV246:画廊

画廊
题意
L幅作品在左边,R幅在右边。从画廊起点正中心出发,到画廊终点正中心结束。
思路
d p [ i ] [ j ] [ 0 ] dp[i][j][0] dp[i][j][0]表示已经解决了左边i幅右边j幅,并且停在左边的走过的距离。
d p [ i ] [ j ] [ 1 ] dp[i][j][1] dp[i][j][1]表示已经解决了左边i幅右边j幅,并且停在右边的走过的距离。
函数 d i s t ( d o u b l e a , d o u b l e b , b o o l s a m e ) dist(double a, double b, bool same) dist(doublea,doubleb,boolsame)求两点之间距离,a和b是和画廊起点的距离,same表示这两点是否在同一边。
则dp转移方程:
d p [ i ] [ j ] [ 0 ] = m i n { d p [ i − 1 ] [ j ] [ 0 ] + d i s t ( l [ i − 1 ] , l [ i ] , 1 ) , d p [ i − 1 ] [ j ] [ 1 ] + d i s t ( r [ j ] , l [ i ] , 0 ) } dp[i][j][0] = min\{ dp[i-1][j][0]+dist(l[i-1],l[i],1), dp[i-1][j][1]+dist(r[j],l[i],0) \} dp[i][j][0]=min{dp[i1][j][0]+dist(l[i1],l[i],1),dp[i1][j][1]+dist(r[j],l[i],0)}
d p [ i ] [ j ] [ 1 ] dp[i][j][1] dp[i][j][1]的转移方程同理。最后加上到达终点的距离。
//第一遍写的时候样例和自测样例都过了 结果0分。。。。太粗心了 自测样例写的也不好,这么丢分真的好窒息

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 5e2 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
const ll mx2 = 2e6 + 10;

double dp[mx][mx][2];//当前在:0-left, 1-right
double l[mx], r[mx];
ll L, R, d, w;

double pf(double a){
	return a*a;
}
double dist(double a, double b, bool same){
	if(same) return sqrt(pf(a-b));
	return sqrt(pf(a-b)+pf(w));
}

void solve(){
	scanf("%lld %lld %lld %lld", &L, &R, &d, &w);
	rep(i,1,L)scanf("%lf", &l[i]);
	rep(i,1,R)scanf("%lf", &r[i]);
	
	dp[1][0][0] = sqrt(pf(w/2.0)+pf(l[1]));
	dp[0][1][1] = sqrt(pf(w/2.0)+pf(r[1]));
	rep(i,2,L) dp[i][0][0]=dp[i-1][0][0]+dist(l[i],l[i-1],1);
	rep(i,2,R) dp[0][i][1]=dp[0][i-1][1]+dist(r[i],r[i-1],1);
	
	rep(i,1,L){
		rep(j,1,R){
			double nv1, nv2;
			nv1 = dp[i-1][j][0]+dist(l[i-1],l[i],1);
			nv2 = dp[i-1][j][1]+dist(r[j],l[i],0);
			dp[i][j][0]=(i==1?nv2:min(nv1,nv2));
			nv1 = dp[i][j-1][1]+dist(r[j-1],r[j],1);
			nv2 = dp[i][j-1][0]+dist(l[i],r[j],0);
			dp[i][j][1]=(j==1?nv2:min(nv1,nv2));
		}
	}
	double nv1 = dp[L][R][0]+sqrt(pf(w/2.0)+pf(d-l[L]));
	double nv2 = dp[L][R][1]+sqrt(pf(w/2.0)+pf(d-r[R]));
	printf("%.2lf\n", min(nv1, nv2));
} 


int main(){
	cin.tie(0); 
	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV244:游园安排

游园安排
题意
最长上升子序列板子题。但是复杂度要求 O ( n l o g n ) O(nlogn) O(nlogn)
思路
d p [ s u b ] dp[sub] dp[sub]表示的是,目前求得的上升子序列长度为sub时的最小字符。

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e6 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;

struct node{
	ll len, sx;
	ll zfc; 
	char name[12];
}nos[mx];
ll id, dp[mx], nex[mx], len;
char input[mx];

ll check(node a, node b){//-1 a<b; 1 a>b
	return strcmp(a.name,b.name);	
}

void solve(){
	scanf("%s", input);
	ll num = strlen(input), i = 0;
	while(i<num){
		id++;
		nos[id].name[0] = input[i];
		nos[id].sx = i;
		ll j = i+1, t = 1;
		while(j<num && input[j]>='a'&&input[j]<='z'){
			nos[id].name[t++]=input[j];
			j++;
		}
		nos[id].name[t] = '\0';
		i = j;
	}
	
	dp[++len] = 1;
	rep(i,2,id){
		ll zuo = 1, you = len, mid;
		while(zuo <= you){
			mid = (zuo+you)/2;
			ll nid = dp[mid];
			if(check(nos[nid], nos[i])>=0LL){
				//nid 大于等于自己i
				you = mid -1; 
			}
			else{
				zuo = mid + 1;
			}
		}
		//zuo = 结果
		if(zuo > len){
			dp[++len] = i;
			nex[i] = dp[len-1];
		} 
		else{
			dp[zuo] = i;
			nex[i] = dp[zuo-1];
		}
	} 
	ll x = dp[len];
	stack<ll>v;
	while(x!=0){
		v.push(x);
		x = nex[x];
	}
	while(!v.empty()){
		printf("%s", nos[v.top()].name);
		v.pop();
	}
	puts("");
	
}

int main(){
//	cin.tie(0); 
//	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
PREV241:奇偶覆盖

奇偶覆盖
题意
n个矩形求奇数次覆盖的面积和偶数次覆盖的面积。
思路
离散化+两次扫描线。第一次求总面积,第二次求奇数覆盖面积。
求总面积的时候,线段树节点里的cover表示当前区间整体覆盖的次数。pushup的时候取min。pushdown的时候加上flag。
求奇数覆盖面积的时候,线段树节点里的cover表示当前区间奇数覆盖的长度。pushup的时候取和。pushdown的时候根据flag判断奇偶性质变化。
之所以求奇数不求偶数的原因是,奇数确保了肯定存在矩形,偶数当cover为0的时候不能确保当前有矩形。
//谁写谁吐血之丑陋的代码

#include<bits/stdc++.h> 
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 2e6 + 10;
const ll inf = 100000 + 10;

struct Line{//从下往上扫描 化边为点 
	ll x1, x2;//实际的边为[x1,x2-1] 
	bool add;//是矩形入边吗 
}; 

ll n, l1[mx], r1[mx], l2[mx], r2[mx];
ll lenx, leny;
vector<ll>vl, vr;
map<ll,ll>lid, rid;

vector<Line>e[mx];

struct node{//线段树 
	ll zuo, you;
	ll cover, flag;
}nos[mx<<1];

ll getval(ll c){
	//nos[c].you+1 - nos[c].zuo 
	return vl[nos[c].you] - vl[nos[c].zuo-1]; 
}

void init(ll c, ll l, ll r){
	nos[c].zuo = l, nos[c].you = r;
	nos[c].cover = nos[c].flag = 0LL;
	if(l == r){
		nos[c].cover = nos[c].flag = 0LL;
		return;
	}
	ll mid = (l+r)>>1;
	init(c<<1,l,mid);
	init(c<<1|1,mid+1,r);
}

void pushup(ll c){
	nos[c].cover = min(nos[c<<1].cover, nos[c<<1|1].cover);
}
void pushdown(ll c){
	ll f = nos[c].flag;
	nos[c].flag = 0;
	nos[c<<1].cover += f, nos[c<<1].flag += f;
	nos[c<<1|1].cover += f, nos[c<<1|1].flag += f;	
}
void change(ll c, ll l, ll r, ll L, ll R, bool add){
	if(L<=l && r<=R){
		if(add) nos[c].cover++, nos[c].flag++;
		else nos[c].cover--, nos[c].flag--;
		return;
	}
	pushdown(c);
	ll mid = (l+r)>>1;
	if(L<=mid) change(c<<1,l,mid,L,R,add);
	if(R>mid) change(c<<1|1,mid+1,r,L,R,add);
	pushup(c);
}
ll query(ll c, ll l, ll r, ll L, ll R){
	if(l == r || nos[c].cover){
		return nos[c].cover?getval(c):0LL;
	}
	pushdown(c);
	ll mid = (l+r)>>1;
	ll ans = 0;
	ans += query(c<<1,l,mid,L,R);
	ans += query(c<<1|1,mid+1,r,L,R);
	pushup(c);
	return ans;
}

ll scanLine(){//扫描线算法
	init(1,1,lenx);
	ll ansS = 0, dy = -1;
	rep(i,1,leny){//从下往上扫描 
		ll num = e[i].size();
		if(num == 0LL)continue;
		//求面积
		ll cx = query(1,1,lenx,1,lenx);
		ll ddy = vr[i-1]-dy;
		if(dy!=-1LL)ansS += cx * ddy;
		//更新 
		rep(j,0,num-1){
			Line cl = e[i][j];
			change(1,1,lenx,cl.x1,cl.x2,cl.add);
		}
		dy = vr[i-1];
	}
	return ansS;
} 


void pushup2(ll c){
	nos[c].cover = nos[c<<1].cover + nos[c<<1|1].cover;
}
void pushdown2(ll c){
	ll f = nos[c].flag;
	nos[c].flag = 0;
	if(f){
		nos[c<<1].cover = getval(c<<1) - nos[c<<1].cover;
		nos[c<<1|1].cover = getval(c<<1|1) - nos[c<<1|1].cover;
	}
	nos[c<<1].flag ^= f;
	nos[c<<1|1].flag ^= f;
}
void change2(ll c, ll l, ll r, ll L, ll R){
	if(L<=l && r<=R){
		nos[c].cover = getval(c) - nos[c].cover;
		nos[c].flag = 1 - nos[c].flag;
		return;
	}
	pushdown2(c);
	ll mid = (l+r)>>1;
	if(L<=mid) change2(c<<1,l,mid,L,R);
	if(R>mid) change2(c<<1|1,mid+1,r,L,R);
	pushup2(c);
}

ll scanLine2(){
	init(1,1,lenx);
	ll ansS = 0, dy = -1;
	rep(i,1,leny){//从下往上扫描 
		ll num = e[i].size();
		if(num == 0LL)continue;
		//求面积
		ll ddy = vr[i-1]-dy;
		if(dy!=-1LL)ansS += nos[1].cover * ddy;
		//更新 
		rep(j,0,num-1){
			Line cl = e[i][j];
			change2(1,1,lenx,cl.x1,cl.x2);
		}
		dy = vr[i-1];
	}
	return ansS;
}
void solve(){
	scanf("%lld", &n);
	rep(i,1,n){
		scanf("%lld %lld %lld %lld",&l1[i], &r1[i], &l2[i], &r2[i]);
		vl.pb(l1[i]);vl.pb(l2[i]);
		vr.pb(r1[i]);vr.pb(r2[i]);
	}
	sort(all(vl));
	sort(all(vr));
	vl.erase(unique(all(vl)),vl.end());
	vr.erase(unique(all(vr)),vr.end());
	lenx = vl.size(), leny = vr.size();
	rep(i,0,lenx-1) lid[vl[i]] = i+1;
	rep(i,0,leny-1) rid[vr[i]] = i+1;
	//离散化数据 
	rep(i,1,n){
		l1[i] = lid[l1[i]], l2[i] = lid[l2[i]];
		r1[i] = rid[r1[i]], r2[i] = rid[r2[i]];
	}
	//数据处理完毕 开始扫描线 
	//预处理 矩形入边出边的数据
	rep(i,1,n){//从下往上扫描 l1l2为x坐标 r1r2为y坐标 
		if(r1[i]>r2[i]) swap(r1[i],r2[i]);
		if(l1[i]>l2[i]) swap(l1[i],l2[i]); 
		if(l1[i]==l2[i])continue;//面积为0 
		Line ru,chu;
		ru.add = true, chu.add = false;
		ru.x1 = l1[i], ru.x2 = l2[i]-1;
		chu.x1 = l1[i], chu.x2 = l2[i]-1;
		e[r1[i]].pb(ru);
		e[r2[i]].pb(chu);
	}

	//扫描线 
	lenx --;//去掉了最大的不会处理的值 
	ll totalS = scanLine();
	ll jiS = scanLine2(); 
	printf("%lld\n%lld\n", jiS, totalS - jiS);
} 

int main(){
//	cin.tie(0); 
//	ios::sync_with_stdio(0);
 
	#ifdef LOCAL
	freopen("1.txt", "r", stdin);
	#endif
	solve();
	#ifdef LOCAL
	fclose(stdin);
	#endif
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值