西北大学2019春季校赛

这场比赛出的题也是感觉很不错的,限于时间和能力,先补上前面所有的中等题。确实还有很长很长的路要走的。

A-辛苦的自愿者

首先对所有的信息按照时间和编号排序,然后二分小姐姐的数量,每次检查的时候用一个队列来模拟小姐姐就可以了。题解一开始给出的是优先队列,但是这道题其实是不需要的,因为每个小姐姐发气球的时间都是相同的,所以队列里面是自然有序的。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=2e5+50;
struct node{ int t,id; }a[N];
bool cmp(node x,node y) {
	if(x.t<y.t) return true;
	if(x.t==y.t&&x.id<y.id) return true;
	return false;
}
ll f[N];
int n,m,t,d;
priority_queue<ll,vector<ll>,greater<ll> >q; 
bool check(int now) {
	memset(f,0,sizeof(f));
	while(!q.empty()) q.pop();
	for(int i=1;i<=n;i++) {
		if(now) { now--;q.push(a[i].t+t); }
		else {
			auto it=q.top();q.pop();
			if(it<=a[i].t) {
				q.push(a[i].t+t);
			}
			else {
				f[a[i].id]+=it-a[i].t;
				q.push(it+t);
				if(f[a[i].id]>=d) return false; 
			}
		}
	}
	return true;
}
int main() {
	scanf("%d%d%d%d",&n,&m,&t,&d);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i].t,&a[i].id);
	sort(a+1,a+n+1,cmp);
	int l=0,r=2e5+1,ans=0;
	while(r-l>1) {
		int mid=(l+r)>>1;
		if(check(mid)) ans=mid,r=mid;
		else l=mid;
	}
	printf("%d\n",ans);
	return 0;
}

C-景子棋

这道题是一个需要分类讨论思维比较清楚的模拟题。对于给定的残局。首先判断是否已经有一方赢了。首先两方有一方赢了必定是终止局面了。不可能出现双方都赢的局面。然后黑棋赢了以后白棋必定是非赢状态并且是黑棋数量减1才是黑棋获胜,否则就是不可能的局面。同理白棋赢了以后黑棋也一定要是非赢状态并且和白棋的数量一样,否则也是不可能的局面。剩下来的情况就是双方都没有获胜,这个时候也要判断一下棋子的数量是否合法(这里官方标程没有判断这点)。接下来检验一下下一步是谁走。先走的那个人可以一步获胜那它就直接获胜,否则后手的那个人有超过两个位置获胜那后手获胜。否则先手堵死后手,局面就是平局。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
char mp[5][5];
bool check(char c) {
	for(int i=1;i<=3;i++) if(mp[1][i]==c&&mp[2][i]==c&&mp[3][i]==c) return true;
	for(int i=1;i<=3;i++) if(mp[i][1]==c&&mp[i][2]==c&&mp[i][3]==c) return true;
	if(mp[1][1]==c&&mp[2][2]==c&&mp[3][3]==c) return true;
	if(mp[1][3]==c&&mp[2][2]==c&&mp[3][1]==c) return true;
	return false;
}
int cal(char c) {
	int ans=0;
	for(int i=1;i<=3;i++)
		for(int j=1;j<=3;j++) {
			if(mp[i][j]!='.') continue;
			mp[i][j]=c;
			if(check(c)) ans++;
			mp[i][j]='.';
		}
	return ans;
}
int main() {
	int Onum=0,Xnum=0;
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	for(int i=1;i<=3;i++)
		for(int j=1;j<=3;j++){
			cin>>mp[i][j];
			if(mp[i][j]=='O') Onum++;
			if(mp[i][j]=='X') Xnum++;
		}
	bool Owin=check('O'),Xwin=check('X');
	if(Xwin) {
		if(Owin) cout<<"impossible"<<endl;
		else if(Onum!=Xnum-1) cout<<"impossible"<<endl;
		else cout<<"B"<<endl;
	}
	else if(Owin) {
		if(Xwin) cout<<"impossible"<<endl;
		else if(Onum!=Xnum) cout<<"impossible"<<endl;
		else cout<<"W"<<endl;
	}
	else if(Xnum!=Onum&&Xnum!=Onum+1) cout<<"impossible"<<endl;
	else {
		int Bcnt=cal('X'),Wcnt=cal('O'); 
		if(Xnum==Onum) {
			if(Bcnt!=0) cout<<"B"<<endl;
			else if(Wcnt>=2) cout<<"W"<<endl;
			else cout<<"draw"<<endl;
		} 
		else if(Xnum==Onum+1) {
			if(Wcnt!=0) cout<<"W"<<endl;
			else if(Bcnt>=2) cout<<"B"<<endl;
			else cout<<"draw"<<endl; 
		}
	}
	return 0;

E-NE的挑战

其实是个并查集的模板题。不过这里有不少并查集的技巧。题意是给出一系列并查集的操作,最后让你求出一个最大的集合。由于这里面用到了并查集的删除的操作,要给每个元素一个虚节点。一开始的时候每个元素的虚节点都是自己。但是被删除的时候就要重新开出来一个新的虚节点。所有的操作全部改成对虚节点的操作。这样问题就好处理了,合并的合并,查询的查询都好说。但是这里还有一个仇恨问题。因为只是最后选取的是没有仇恨的势力。可以先离线保存。到最后的时候再判断是否可以选择整个势力。是个很有启发的题目。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=2e5+50;
int fa[N],sz[N],to[N<<5];
int cnt;
int find(int x) { return fa[x]==x?fa[x]:fa[x]=find(fa[x]); }
void unity(int x,int y) { 
	int u=find(to[x]),v=find(to[y]); 
	if(u==v) return; 
	else { fa[v]=u,sz[u]+=sz[v],sz[v]=0;return;} }
void update(int x,int y) { sz[find(to[x])]--,to[x]=++cnt,sz[to[x]]=1,fa[to[x]]=cnt;unity(x,y); }
vector<pair<int,int> > v; 
bool resent[N];
int main() {
	int n,m;
	scanf("%d%d",&n,&m);
	cnt=n;
	for(int i=1;i<=n;i++) fa[i]=i,to[i]=i,sz[i]=1;
	for(int x,y,opt,i=1;i<=m;i++) {
		scanf("%d",&opt);
		if(opt==1) { scanf("%d%d",&x,&y);unity(x,y);} 
		if(opt==2) { scanf("%d%d",&x,&y);update(x,y); }
		if(opt==3) { scanf("%d",&x);printf("%d\n",sz[find(to[x])]-1); }
		if(opt==4) { scanf("%d%d",&x,&y);if(find(to[x])==find(to[y])) puts("Yes");else puts("No"); }
		if(opt==5) { scanf("%d%d",&x,&y);v.push_back({x,y}); }
	}
	int ans=-1;
	for(int i=0;i<v.size();i++) { if(find(to[v[i].F])==find(to[v[i].S])) resent[find(to[v[i].F])]=true; }
	for(int i=1;i<=n;i++) { if(!resent[find(to[i])]) ans=max(ans,sz[find(to[i])]); }
	printf("%d\n",ans);
	return 0;
}

F-三生三世

这道题一开始的时候完全没有思路。而且实际上也不会写搜索。看了题解以后大概尝试了一下。大意就是说给出两个序列的全排列,然后让你判断这两个序列它们在序列里面差多少。然后对于其中的一个串。我们直接从头开始搜索。给每一个位置去填。如果已经能够字典序小于目标串的话。那么就直接组合数计算一下,后面的所有可能。否则的话就继续搜索下去。由于这里的剪枝非常强力。继续只保留了前面位数字典序相同的,所以复杂度应该是近似于线性的。

#pragma GCC optimize(3,"Ofast","inline")
#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=2e5+5; 
const ll mod=1e9+7;
string str1,str2,str;
int n;
ll F[N],Finv[N],Set[30];
ll fpow(ll x,ll y) {
	ll ans=1;
	while(y) {
		if(y&1) ans*=x,ans%=mod;
		x*=x,x%=mod;
		y>>=1;
	}
	return ans%mod;
}
void init() {
	F[0]=Finv[0]=1;
	for(ll i=1;i<N;i++) F[i]=(F[i-1]*i)%mod;
	Finv[N-1]=fpow(F[N-1],mod-2);
	for(ll i=N-2;i>=1;i--) Finv[i]=Finv[i+1]*(i+1)%mod;
}
ll cal(ll n,ll m) {
	if(m<0||m>n) return 0;
	else return F[n]*Finv[m]%mod*Finv[n-m]%mod;
}
ll dfs(int len,int now) {
	if(len==n-1) return 0;
	if(len!=-1&&now<str[len]-'A') {
		ll tmp=1,nd=n-len-1;
		for(int i=0;i<26;i++)
			if(Set[i]) tmp*=cal(nd,Set[i])%mod,tmp%=mod,nd-=Set[i];
		return tmp;
	}
	ll tmp=0;
	for(int i=0;i<26;i++) {
		if(Set[i]&&i<=str[len+1]-'A') {
			Set[i]--;
			tmp+=dfs(len+1,i)%mod,tmp%=mod;
			Set[i]++;
		}
	}
	return tmp;
}
ll solve(string s) {
	str=s;
	memset(Set,0,sizeof(Set));
	for(int i=0;i<n;i++) Set[str[i]-'A']++;
	return dfs(-1,30);
}
int main() {
	init();
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>str1>>str2;
	printf("%lld\n",(solve(str1)-solve(str2)+mod)%mod);
	return 0;
}

G-房间迷宫

题意其实就是说每个房间都能转移到一下一个房间然后付出一个代价,但是也可以花费一些代价去走到当前位置加上一个数的任意一个因子的位置。这道题其实就是一道裸的最短路。先预处理一下每个数的因子,根据题意建图。跑一遍最短路最后加上第一个房间的初始值就好了。
但是这道题用前向星建图确实出现莫名其妙的RE问题,改成vector存图之后才能过,这个体现了vector的优越性,不需要人为设定空间。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=2e5+7;
int a[N],b[N],c[N],d[N];
ll dis[N];
int n;
struct edge{ int v;ll w; };
struct node{
	int u;ll dis;
	bool operator <(const node& rhs) const{
		return dis>rhs.dis;
	}
};
priority_queue<node> q;
vector<edge> G[N];
vector<int> Fac[N];
void solve() {
	for(int i=1;i<=n;i++) dis[i]=1e18;
	dis[1]=0;
	q.push({1,dis[1]});
	while(!q.empty()){
		auto it=q.top();q.pop();
		int u=it.u;ll Dis=it.dis;
		if(Dis>dis[u]) continue;
		for(auto edge:G[u]) {
			int v=edge.v;ll w=edge.w;
			if(dis[v]>dis[u]+w) {
				dis[v]=dis[u]+w;
				q.push({v,dis[v]});
			}
		}
	}
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=(int)2e5;i++)
		for(int j=i;j<=(int)2e5;j+=i)
			Fac[j].push_back(i);
	for(int i=1;i<=n;i++) scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
	for(int i=1;i<=n;i++) {
		G[i].push_back({d[i],a[d[i]]});
		for(int j=0;j<Fac[c[i]].size();j++) {
			if(i+Fac[c[i]][j]<=n) G[i].push_back({i+Fac[c[i]][j],b[i]+a[i+Fac[c[i]][j]]}); 
		}
	}
	solve();
	if(dis[n]==1e18) printf("-1\n");
	else printf("%lld\n",dis[n]+a[1]);
	return 0;
}

H-算法入门

我也不知道这道题算不算是算法入门。给出了n个数字,想询问你是否能找到一堆数字之和可以被m整除,其中m是不超过n的。首先考虑整除只要考虑每个数模上m就可以了。
这样我们就会发现这个n个数都是在0到m-1之间的。如果这当中有0,这就直接找到了这个数,否则n个数就在1到m-1这m-1个数之间。不妨考虑n个数按照任意顺序构成了n个前缀,这n个前缀也必定有两个前缀是模m同余的。找到这两个前缀,直接相减这一段连续的数就必定被m整除。所以只要直接输出YES就好了。

I-多米诺骨牌

这道题让我学习到了一些新的姿势。题意是想问你每次都有一定的概率把一个牌子给立起来,但是如果没有立起来就要推倒之前所有的重来,想问你把所有牌子立起来的最大期望是多少。
注意到要求最大期望,肯定是把容易倒的牌子放在后面立更大。先对牌子排个序,然后不妨记dp[n]表示我把前n个牌子立起来的期望数。那么无论如何我至少把第n个立起来是1次。如果成功那就什么也没有发生,如果失败就必须要推到重来,于是就有dp[n]=dp[n-1]+1+a/b*0+(1-a/b)dp[n]。推出式子之后随便算一下就好了。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=2e5+50;
const ll mod=1e9+7;
ll dp[N]; 
struct node{ ll a,b; }cd[N];
bool cmp(node x,node y) { return x.a*y.b>=y.a*x.b; }
ll fpow(ll x,ll y) {
	ll ans=1;
	while(y) {
		if(y&1) ans*=x,ans%=mod;
		x*=x,x%=mod;
		y>>=1;
	}
	return ans;
} 
int main() { 
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld",&cd[i].a,&cd[i].b);
	sort(cd+1,cd+n+1,cmp);
	dp[0]=0;
	for(int i=1;i<=n;i++) {
		dp[i]=(dp[i-1]+1) * cd[i].b%mod *fpow(cd[i].a,mod-2) %mod;
	}
	printf("%lld\n",dp[n]);
	return 0;
}

K-小小马

最终的胜负只和棋盘的奇偶性有关。如果是奇数棋盘的话首先先占上中间的那个位置,然后只要和对手走中心对称的位置就行。如果是偶数棋盘,对手始终可以和你走镜像对称的位置。奇数必胜,偶数必败。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值