2019杭州师范大学校赛

有幸能够参加有这么多优秀队伍的比赛,确实也通过这个看出了自身很多的不足,无论是浙江高校还是浙江的高中生,他们的训练水平还是远远在我们之上的。唯有好好努力,才能创造机会。

I-Little Sub and Triangles

题意很简单,就是给出了一堆平面上的格点,每次询问会给一个范围,问落在这个范围里面的由这些格点组成的三角形的数量是多少。
首先先想到求出所有的三角形,然后再排序。排序之后的每次查询只要二分就可以了。
但是这道题要注意的地方有:
1.给出了三个点求三角形的面积用向量的叉积是比较方便的。两个向量分别是(x1,y1),(x2,y2)的时候,向量叉积是x1y2-y1x2。而用sqrt的效率比较低,这里会超时。
2.格点三角形一定是1/2的整数倍,因为不妨把这个三角形补全,你很快就会发现多出来的三角形一定是1/2的整数倍。所以这里如果让所有的数都乘上2,这道题就转化为了全整数的问题,就便于处理了。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define Rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef long double ld;
const int inf=0x3f3f3f3f;
const ll INF=9e18;
const int N=300;
int cnt=0;
ll tri[N*N*N],x[N],y[N];
int main() {
	int n,q;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%lld%lld",&x[i],&y[i]);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			for(int k=j+1;k<=n;k++)
				tri[++cnt]=abs((x[j]-x[i])*(y[k]-y[i])-(y[j]-y[i])*(x[k]-x[i]));
	sort(tri+1,tri+cnt+1);
	while(q--) {
		ll l,r;
		scanf("%lld%lld",&l,&r);
		l*=2,r*=2;
		int pl=lower_bound(tri+1,tri+cnt+1,l)-tri;
		int pr=upper_bound(tri+1,tri+cnt+1,r)-tri;
		printf("%d\n",pr-pl);
	}
	return 0;
}

H-Little Sub and Counting

题意说给出一些数,然后要求出对于每个给定的数x,满足 x y &gt; y x x^{y}&gt;y^{x} xy>yx
的数目的。然后这个题考虑变成 x l n x \frac{x}{lnx} lnxx求导,然后我们容易发现 e e e是它的一个极值点,只需要特判一下前面的1,2,3,然后对后面的数直接二分找到第一个大于它们的数就可以了。
需要注意的是如何写好一个结构体的二分,结构体的二分比较的一定是一个结构体,需要写构造函数放进去二分。

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;
const int N=1e5+50;
struct node{ 
	int x,id,ans; 
	node() {}
	node(int x,int id=0,int ans=0):x(x),id(id),ans(ans) {} 
	bool operator <(const node& rhs) const {
		return x<rhs.x;
	}
}a[N];
bool cmp1(node a,node b)  { return a.x<b.x; }
bool cmp2(node a,node b) { return a.id<b.id; }
int main() {
	int n,cnt1=0,cnt2=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i].x),a[i].id=i;
	sort(a+1,a+n+1,cmp1);
	for(int i=1;i<=n;i++) if(a[i].x==1) ++cnt1;
	for(int i=1;i<=n;i++) if(a[i].x==2) ++cnt2;
	for(int i=1;i<=n;i++) {
		if(a[i].x==1) { a[i].ans=0;continue; }
		else if(a[i].x==2) {
			int pos=upper_bound(a+1,a+n+1,node(4))-a;
			a[i].ans=cnt1+n-pos+1; 
		}
		else if(a[i].x==3) {
			int pos=upper_bound(a+1,a+n+1,node(3))-a;
			a[i].ans=cnt1+cnt2+n-pos+1;
		}
		else {
			int pos=upper_bound(a+1,a+n+1,node(a[i].x))-a;
			a[i].ans=cnt1+n-pos+1;
		}
	} 
	sort(a+1,a+n+1,cmp2);
	for(int i=1;i<=n;i++) printf("%d%c",a[i].ans,i==n?'\n':' ');
	return 0;
}

B-Little Sub and Triples

这道题是给我印象最深的一道题,题意其实化简一下就是说对于一个数组,每次都会去询问一段区间,然后问这个区间是否可以存在三个数,它们能够构成三角形。先考虑对区间排序。那么对于这个每个数,只要考虑最大的比它小的那两个数是否大于它就可以了。但是每次都对区间排序的话,这个复杂度会非常大。这里需要逆向思考这个问题,如果这个区间是NO的话,但是要保证里面的所有的数字都尽量小,那么这个区间实际上就是一个Fibonacci数列,而Fibonacci数列很快就会增长超过int的范围。所以说对于长度超过60的区间,直接就可以判断是YES,对于剩下来的情况再做排序处理就好了。
这里的逆向思考的思维确实很精妙。

#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 a[N];
vector<int> v;
int main() {
	int n,q;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	while(q--) {
		int l,r;
		scanf("%d%d",&l,&r);
		if(r-l+1<=2) printf("NO\n");
		else if(r-l+1>=60) printf("YES\n");
		else {
			v.clear();
			for(int i=l;i<=r;i++) v.push_back(a[i]);
			sort(v.begin(),v.end());
			bool exist=false;
			for(int i=0;i<v.size()-2;i++) {
				if(v[i]>v[i+2]-v[i+1]) { exist=true;break; }
			}
			if(exist) printf("YES\n");
			else printf("NO\n");
		} 
	}
	return 0;
}

E-Little Sub and Traveling

这道题是一个非常有想法的题目。位置从0开始。然后每次都可以转移自己的位置 i i i,要么转移为 2 ∗ i + 1 m o d n 2*i+1mod n 2i+1modn,要么转移为 2 ∗ i m o d n 2*imod n 2imodn。要求字典序最大的汉密尔顿回路。首先对于奇数的情况是无解的,证明是这样的:不妨设 n = 2 ∗ k + 1 n=2*k+1 n=2k+1,1.最终要回到0,必须要通过k转移。2 . 2 k 2k 2k 这个数也只能通过 k k k 来转移。3.第一次走到 k k k的时候,若直接返回,则 2 k 2k 2k没有被走过。若走到 2 k 2k 2k则无法再回到0,因为必须要从 k k k返回,但是 k k k已经被走过一次。所以综上所述。奇数是无解的。那么考虑偶数的构造。主要这模n运算对 i i i i + n / 2 i+n/2 i+n/2是完全等价的。不妨把这两个点并成一个点。这样每个点都有两个入度和两个出度。而这恰好就是一个欧拉回路。在欧拉回路上做一遍逆序dfs。

#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=1e4+50;
bool vis[N];
int n;
vector<int> ans;
void dfs(int now) {
	if(!vis[(2*now+1)%n]) {
		vis[(2*now+1)%n]=1;
		dfs((2*now+1)%(n/2));
		ans.push_back((2*now+1)%n);
	}
	if(!vis[(2*now)%n]) {
		vis[(2*now)%n]=1;
		dfs((2*now)%(n/2));
		ans.push_back((2*now)%n);
	}	
}
int main() {
	scanf("%d",&n);
	if(n&1) { puts("-1"); return 0;}
	else {
		vis[0]=1;
		ans.push_back(0);
		dfs(0); 
		ans.push_back(0);	
	}
	for(int i=ans.size()-1;i>=0;i--)
		printf("%d%c",ans[i],i==0?'\n':' ');
	return 0;

I-Little Sub and Enigma

这道题在场上并没有读懂题意,其实是要求S和T满足一个双射,如果可以满足就输出全部的映射。首先先维护S串中的每个字符不能指向两个不同的字符,然后再维护T串每个字符也不能被两个不同的字符所指。
但是这道题需要注意的事情就是当你确定了25组关系的时候,剩下来的那一组也会被唯一确定,这种情况需要特判。

#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=1e6+50;
char s1[N],s2[N];
int a[200],b[200];
int main() {
	int cnt=0;
	scanf("%s%s",s1+1,s2+1);
	int len=strlen(s1+1);
	for(int i=1;i<=len;i++) {
		if(!a[s1[i]]) a[s1[i]]=s2[i],++cnt;
		else if(a[s1[i]]&&a[s1[i]]!=s2[i]) return 0*printf("Impossible\n");
	}
	for(int i=1;i<=len;i++) {
		if(!b[s2[i]]) b[s2[i]]=s1[i];
		else if(b[s2[i]]&&b[s2[i]]!=s1[i]) return 0*printf("Impossible\n");
	}
	if(cnt==25) 
	for(int i='a';i<='z';i++) 
		for(int j='a';j<='z';j++){
			if(!a[i]&&!b[j]) a[i]=j;
		}
	for(int i='a';i<='z';i++) {
		if(a[i]) printf("%c->%c\n",(char)i,(char)a[i]); 
	}
	return 0;
}

M-Little Sub and Johann

这个是一个挺好的博弈题,其实只要好好找规律很快就能发现规律。对于石子个数只有1的堆SG为1,然后对于质数SG是这个质数在质数表里面的位置+1,对于合数SG是它的最小质因子。恰好可以用线筛筛出来所有范围内的SG函数。
接下来证明一下这个SG函数。对于石子个数为1是显然的。而质数则是由前面所有的状态转移,因此是连续单调递增的。然后考虑一个合数所有被转移的情况,是从0到最小质因数-1,然后这些质数的SG函数都是连续增加的,所以第一个无法被转移到的位置就是最小质因数,所以就是SG[lPF]
其实不是一道很难的题,所以还是姿势水平不太够。

#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=1e6+5;
int pri[N],sg[N],cnt=0;
bool vis[N];
void getsg() {
	sg[1]=1;
	for(int i=2;i<=(int)1e6;i++) {
		if(!vis[i]) pri[++cnt]=i,sg[i]=cnt+1;
		for(int j=1;j<=cnt&&i*pri[j]<=(int)1e6;j++) {
			vis[i*pri[j]]=true;
			sg[i*pri[j]]=sg[pri[j]];
			if(i%pri[j]==0) break;
		}
	}
}
int main() {
	getsg();
	int line;
	scanf("%d",&line);
	while(line--) {
		int n,ans=0;
		scanf("%d",&n);
		for(int x,i=1;i<=n;i++) scanf("%d",&x),ans^=sg[x];
		if(!ans) printf("Long live with King Johann!\n");
		else printf("Subconscious is our king!\n"); 
	}
	return 0;
}

G-Little Sub and Piggybank

这是一道非常有趣的dp题,可以学习让我们学习到不少东西。题意是说每个商品都有一个价格,并且每个商品都能带来一定的快乐值,然后每个时刻都可以投入任意多数量的钱,但是必须要保证后面投入的不能超过前面投入的钱,而且买任何商品都必须是刚刚好这个商品价格的钱数才能购买。
首先贪心考虑如果我要买的商品是 a k 1 , a k 2 . . . a k m a_{k_{1}},a_{k_{2}}...a_{k_{m}} ak1,ak2...akm,那么最优的做法应该是在中间的每一天都购买 a k 2 − a k 1 / k 2 − k 1 a_{k_{2}}-a_{k_{1}}/k_{2}-k_{1} ak2ak1/k2k1。记dp[i][j]是我最后一个买的商品是i,倒数第二个买的商品是j的最大值,dp[i][j]=max(dp[i][j],dp[j][k]+v[i]),这里k<=j<=i。然后由于满足条件,a[i]/(i-j)<=a[j]/(j-k),化简一下就可以分离k使得k>=j-a[j]/a[i]*(i-j),这里每次处理出来dp[i][j]以后都可以处理出g[i][j]=max{dp[i][i-1]…dp[i][j]},这样复杂度就可以被优化到O(n^2)。
1.转移的时候不仅要保证k<j,也要保证g[j][k]是存在的,因为并不是这样的状态就一定是合法的或者有意义的。
2.要注意a[i]可能为0的情况,这个时候会出现0/0的情形,这个时候特判可以是由任何状态转移过来的。

#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=2e3+50;
int a[N],v[N],f[N][N],g[N][N];
int main() {
	int n,k; 
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&v[i]);
	for(int i=1;i<=n;i++) {
		f[i][0]=v[i];
		for(int j=1;j<i;j++) {
			if(a[i]==0) k=0;
			else k=max(ceil(j-1.0*a[j]/a[i]*(i-j)),0.0);
			if(k<j&&g[j][k]) f[i][j]=max(f[i][j],g[j][k]+v[i]);
		}
		for(int j=i-1;j>=0;j--) {
			g[i][j]=max(g[i][j+1],f[i][j]);
		} 
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,g[i][0]);
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值