【过题记录】 7.24

昨天不知道为什么有点头痛,就先回去休息了,没更新.(其实是比赛打炸了,心态有点崩( )

Bridging the Gap 2

算法:贪心,数学归纳
i i i个人最多划的趟数是 ( a [ i ] − 1 ) / 2 (a[i]-1)/2 (a[i]1)/2
不难发现,将所有人都送往对岸的最小趟数是S= ( n − r ) / ( r − l ) (n-r)/(r-l) (nr)/(rl)
而一趟至少要有l个人会消耗一次趟数
所以总的趟数代价就是S*l
所以能够运送的必要条件是 ∑ m i n ( a [ i ] , S ) > = S ∗ l \sum min(a[i],S)>=S*l min(a[i],S)>=Sl
接下来运用贪心证明这个是充分条件:
每次我们选择最大的l个人的趟数-1
那么上述式子就满足:
∑ m i n ( a [ i ] − 1 , S ) > = ( S − 1 ) ∗ l \sum min(a[i]-1,S)>=(S-1)*l min(a[i]1,S)>=S1l
也就是每一趟都有人能过河,直到最后


#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 5e5+100;
int n,l,r;
int a[N];

int minn;

signed main(){
	cin>>n>>l>>r;
	if ((n-r)%(r-l) == 0) minn = (n-r)/(r-l); else minn = (n-r)/(r-l)+1;
	for (int i = 1; i <= n; i++){
		int x; cin>>x; a[i] = (x-1)/2;
	}
	int S = 0;
	for (int i = 1; i <= n; i++) S+=min(a[i],minn);
	if (S >= minn*l) cout<<"Yes"; else cout<<"No";
	return 0;
}

Rigged Games

对于每一轮,我们对于每一个位置可以二分/倍增出相应的胜利位置,以及记录当前0/1的胜利情况
而后对于每一个位置,我们对于局数b进行倍增,找出对应的位置以及0/1的胜负情况。
思路比较好想,代码细节挺多,实现有点难度

#include<bits/stdc++.h>
using namespace std;

const int N = 4e5+10;

int n,a,b;
char s[N];

int z[N],o[N];
typedef pair < int , int > pii;
pii su[N][30];
int Ne[N][30];

bool check(int ed,int x){
	int O = o[ed]-o[x-1],Z = z[ed]-z[x-1];
	if (O < a && Z < a) return 0; return 1;
}

#define fi first
#define se second
#define mp make_pair

pii Find(int x){
	int l = 0 , r = 2*a-1;
	while (l+1<r){
		int Mid = l+r>>1;
		int ed = x+Mid;
	    int O = o[ed]-o[x-1],Z = z[ed]-z[x-1];
		if (O < a && Z < a) l = Mid;
		else r = Mid;
	}
	int po;
	if (check(x+l,x)) po = x+l; else po = x+r;
	int O = o[po]-o[x-1] , Z = z[po] - z[x-1];
	int op;
	if (O >= a) op = 0; else op = 1;
	po = (po+1)%n; 
	if (po == 0) po = n;
	return mp(po,op);
}

void Pre(){
	for (int i = 1; i < 30; i++)
	  for (int j = 1; j <= n; j++){
	      Ne[j][i] = Ne[Ne[j][i-1]][i-1];
	      int x1 = su[j][i-1].fi+su[Ne[j][i-1]][i-1].fi , x2 = su[j][i-1].se+su[Ne[j][i-1]][i-1].se;
	      su[j][i] = mp(x1,x2);
	  }
}

int Jump(int x){
	int maxx = 0;
	for (int i = 0; i < 30; i++){
		if ((1<<i) > 2*b-1) break;
		maxx = i;
	}
	int now = x;
	int X0 = 0 , X1 = 0; 
	for (int i = maxx; i >= 0; i--){
		int ne = Ne[now][i]; int x0 = su[now][i].fi , x1 = su[now][i].se;
		if (X0+x0 < b && X1 + x1 < b) now = Ne[now][i] , X0 = X0+x0 , X1 = X1+x1;
	}
	X0 = X0+su[now][0].fi , X1 = X1+su[now][0].se;
	if (X0 >= b) return 0; else return 1;
}

int main(){
	cin>>n>>a>>b;
	cin>>(s+1);
	for (int i = n+1; i <= n+2*a-1; i++) s[i] = s[i-n];
    int len = strlen(s+1);
	for (int i = 1; i <= len; i++) o[i] = o[i-1]+(s[i] == '0'),z[i] = z[i-1]+(s[i] == '1');
	for (int i = 1; i <= n; i++){
		pii po = Find(i);
		int x0 = 0,x1 = 0; if (po.se == 0) x0 = 1; else x1 = 1;
		su[i][0] = mp(x0,x1);
		Ne[i][0] = po.fi;
	}
	Pre();
	for (int i = 1; i <= n; i++){
		cout<<Jump(i);
	}
	return 0;
}

Treasure Hunting


Gourmet choice

有两种思路
第一种就是将所有相等的点;利用并查集缩成一个点,然后拓扑排序
如果是维护小于关系,那就是取max,如果是维护大于关系,那就是取min

第二种就是借鉴查分约束的思想
如果是小于关系,就是最长路
如果是大于关系,就是最短路
相等的情况就是互相连边。

下面代码采用第一种思路

#include<bits/stdc++.h>
using namespace std;

const int N = 5010;
int n,m,tot;
int fa[N];
string s[N];

int getfa(int x){
	return x == fa[x]?fa[x]:fa[x] = getfa(fa[x]);
}

void Merge(int x,int y){
	int X = getfa(x) , Y = getfa(y);
	fa[X] = Y; 
}

int du[N];
vector < int > a[N];
#define pb push_back
queue < int > q;
bool vi[N];
int num[N];

int main(){
	scanf("%d %d",&n,&m);
	tot = n+m;
	for (int i = 1; i <= tot; i++) fa[i] = i;
	for (int i = 1; i <= n; i++){
		cin>>s[i];
		for (int j = 0; j < s[i].size(); j++){
			char ch = s[i][j];
			if (ch == '=') Merge(i,j+n+1);
		}
	}
	for (int i = 1; i <= n; i++){
		for (int j = 0; j < s[i].size(); j++){
			char ch = s[i][j];
			if (ch == '<'){
				int X = getfa(i) , Y = getfa(j+n+1);
				a[X].pb(Y); du[Y]++;
			}
			if (ch == '>'){
				int X = getfa(i) , Y = getfa(j+n+1);
				a[Y].pb(X); du[X]++;
			}
		}
	}
	for (int i = 1; i <= tot; i++)
	  if (du[getfa(i)] == 0 && !vi[getfa(i)])
	    q.push(getfa(i)) , vi[getfa(i)] = 1,num[getfa(i)] = 1;
	
	int Tot = 0;
	for (int i = 1; i <= tot; i++)
	  if (fa[i] == i) Tot++;
	int cnt = 0; 
	while (q.size()){
		cnt++;
		int x = q.front(); q.pop(); vi[x] = 0;
		for (int i = 0; i < a[x].size(); i++){
			int y = a[x][i];
			du[y]--; num[y] = max(num[y],num[x]+1);
			if (du[y] == 0 && !vi[y])
			  q.push(y),vi[y] = 1;
		}
	}
	if (cnt != Tot){
		cout<<"No"; return 0;
	}
	cout<<"Yes"<<endl;
	for (int i = 1; i <= n; i++) cout<<num[getfa(i)]<<' '; cout<<endl;
	for (int i = n+1; i <= tot; i++) cout<<num[getfa(i)]<<' ';
	return 0;
}

Famil Door and Brackets

本题要求求方案数,而且他的数据范围n-m<=2000就告诉你这道题大概率是dp
这道题显然跟(与)的个数有关
我们就设 f [ i ] [ j ] f[i][j] f[i][j]表示前i个位置,有j个多余的(的方案数
这个转移就很显然: f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i − 1 ] [ j + 1 ] f[i][j]=f[i-1][j-1]+f[i-1][j+1] f[i][j]=f[i1][j1]+f[i1][j+1]
分别表示这个位置放(与放)
需要注意的是 f [ i ] [ 0 ] = f [ i − 1 ] [ 1 ] f[i][0]=f[i-1][1] f[i][0]=f[i1][1]

而后就是枚举前一个数放的括号的个数以及前一个位置(的个数,利用相等的关系得到右边的)的个数
这边需要明白一点就是)多的个数其实是和(多的个数相等的,翻转一下即可。
然后就是利用乘法原理将两边相乘。
本题还有一个注意点就是由于合法的括号序列要求(的前缀和一直大于0,所以左边括号序列的(个数其实是有最低要求的,就是原串中)最多的个数。

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 2e3+100;
const int P = 1e9+7;
int n,m;
int cnt = 0;
string s;
int f[N][N];

signed main(){
	cin>>n>>m;
	int now = 0;
	f[0][0] = 1;
	for (int i = 1; i <= n-m; i++){
		for (int j = 0; j <= i; j++)
		  if (j == 0) f[i][j]+=f[i-1][1],f[i][j]%=P;
		  else f[i][j] = f[i-1][j-1]+f[i-1][j+1],f[i][j]%=P;
	}
	cin>>s;
	for (int i = 0; i < s.size(); i++){
		if (s[i] == '(') now--; else now++;
		cnt = max(cnt,now);
	}
	int ans = 0;
	for (int i = 0; i <= n-m; i++){
		int Min = max(0ll,cnt);
		for (int j = Min; j <= i; j++)
		  if (j-now <= 2000 && j-now >= 0)ans = ans+((f[i][j]*f[n-m-i][j-now])%P) , ans%=P;
	}
	cout<<ans;
}
  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值