Diorvh

导航

【日记】12.23/【题解】CF Edu78

12.23日记

DP

  1. 洛谷P1280:工作日有N分钟,有K个任务,每个任务从\(p_i\)分钟开始,持续\(t_i\)分钟,每一时刻如果有多个任务要完成,则可以任选一个,但只能干一个工作,问如何选取任务,使得空暇时间最多。数据1e4。

思路:dp[i]表示i-N分钟中,最大的空闲时间。那么从后往前遍历每个任务,这个是无后效性的,与前面的任务怎么选是无关的。那么首先对任务开始时间从大到小排序,再依次从时间n-1开始dp即可。

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
struct T{
	int st,dur;
	bool operator<(const T &x)const{
		return st>x.st;
	}
};
struct Task{
	int n,k,dp[M];
	T a[M];
	void init(){
		scanf("%d%d",&n,&k);
		for(int i=1;i<=k;++i)
			scanf("%d%d",&a[i].st,&a[i].dur);
	}
	void run(){
		sort(a+1,a+k+1);
		int p=1;
		for(int i=n;i>=1;--i)
			if (a[p].st!=i)
				dp[i]=dp[i+1]+1;
			else
				while(a[p].st==i)
					dp[i]=max(dp[i],dp[i+a[p].dur]),++p;
		printf("%d\n",dp[1]);
	}
}task;
int main(){
	task.init(),task.run();
	return 0;
}

CF Edu78

A. Shuffle Hashing

题意:给字符串s和h,s的hash定义为,将s随机打乱,之后加上一个任意前缀和一个任意后缀,得到的字符串即为s的hash,现询问h是否可以为s的hash。

思路:遍历所有h的长为len(s)的区间,统计各个字符个数,如果和s的各个字符个数一样,那么就是可以。时间复杂度\(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int M=1e2+50;
struct T{
	int ans[28],now[28],len1,len2;
	char s1[M],s2[M];
	void init(){
		for(int i=0;i<26;++i)
			ans[i]=now[i]=0;
		scanf("%s%s",s1,s2);
		len1=strlen(s1),len2=strlen(s2);
	}
	inline bool check(){
		for(int i=0;i<26;++i)
			if (ans[i]!=now[i])
				return false;
		return true;
	}
	void run(){
		init();
		if(len2<len1){
			printf("NO\n");
			return;
		}
		for(int i=0;i<len1;++i)
			++ans[s1[i]-'a'],++now[s2[i]-'a'];
		int p=len1;
		while(p<len2&&!check())
			--now[s2[p-len1]-'a'],++now[s2[p]-'a'],++p;
		if (check())
			printf("YES\n");
		else
			printf("NO\n");
	}
}t;
int main(){
	int T;
	scanf("%d",&T);
	for(int i=1;i<=T;++i)
		t.run();
	return 0;
}

B. A and B

题意:给两个数a和b,第k次操作为选定一个数,将其加上k。输出使得a和b相等的最少步数。

思路:求出a和b的差d,那么找到第一个x,使得x(x+1)/2>=d(也就是疯狂给那个小的数加),若d!=0,则此时a和b还是不一样。那么问题就转化为了,将x(x+1)/2分成两个数,使其相差d。这就要看x(x+1)/2+d是否为偶数,若为奇数,则还需要继续加,直到为偶数为止,这样才能分成整数。很容易理解一定加了不超过2次。

时间复杂度\(O(\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
struct T{
	int a,b,d;
	void init(){
		scanf("%d%d",&a,&b);
		d=abs(a-b);
	}
	bool check(int x){
		if (1LL*x*(x+1)/2>=d)
			return true;
		return false;
	}
	void run(){
		init();
		int l=0,r=1e5+20;
		while(l!=r){
			if (check(mid))
				r=mid;
			else
				l=mid+1;
		}
		while((1LL*l*(l+1)/2+d)%2==1)
			++l;
		printf("%d\n",l);
	}
}t;
int main(){
	int T;
	scanf("%d",&T);
	for(int i=1;i<=T;++i)
		t.run();
	return 0;
}

C.Berry Jam

题意:给2n个罐头,一开始站在n和n+1之间,每个罐头为1或者2,可以选择吃掉i-n和n+1-j两个区间内的所有罐头。问最少需要吃掉多少个罐头,才能使得剩下的1和2的数量相等。

题解:假设1个数>2个数,那么首先计算出要额外吃掉的1的个数,记为x。之后计算的时候碰到1就+1,碰到2就-1。首先计算n往左走,每走过一个就+1/-1。可以想到,如果i处为sum,j处也为sum,而且i<j,那么i一定没用。所以对于每个sum,只需要记录第一个出现的j即可。那么可以将出现的位置存入数组或者队列。对右边也一样处理。之后枚举左0右x,左1右x-1……左x右0,根据记录的位置计算出每种情况要吃的罐头的数量,取最小值即可,注意要处理左边可能没有x的情况。

时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
#define tr(x) (x==pd?1:-1)
struct pl{
	int val,num;
	pl(int a=0,int b=0):val(a),num(b){};
};
struct T{
	int n,a[M*2],num[3],pd,dt,sum,ans;
	void init(){
		scanf("%d",&n);
		num[1]=num[2]=0;
		for(int i=1;i<=2*n;++i)
			scanf("%d",&a[i]),++num[a[i]];
		dt=abs(num[1]-num[2]),ans=1e9+7;
	}
	int run(){
		init();
		if (num[1]==num[2])
			return 0;
		if (num[1]>num[2])
			pd=1;
		else
			pd=2;
		deque<pl> l,r;
		sum=0;
		l.push_back(pl(0,0));
		for(int i=n;i>=1;--i){
			sum+=tr(a[i]);
			if (sum>l.back().val)
				l.push_back(pl(sum,n-i+1));
		}
		sum=0;
		r.push_back(pl(0,0));
		for(int i=n+1;i<=2*n;++i){
			sum+=tr(a[i]);
			if (sum>r.back().val)
				r.push_back(pl(sum,i-n));
		}
		while(!l.empty()){
			if (l.front().val>dt)
				break;
			while(r.back().val>dt-l.front().val)
				r.pop_back();
			if (r.back().val+l.front().val==dt)
				ans=min(ans,l.front().num+r.back().num);
			l.pop_front();
		}
		return ans;
	}
}t;
int main(){
	int T;
	scanf("%d",&T);
	for(int i=1;i<=T;++i)
		printf("%d\n",t.run());
	return 0;
}

D. Segment Tree

题意:给n个点,每个点表示一个区间\([l_i,r_i]\),保证所有的l,r均不重复。若两个区间有重叠但不包含,则两个点之间有边。问这个图是否是一颗树。

思路:这也算是一道数据结构题了,结果不会做………………

区间按照l从小到大排序,每读入一个区间就将右端点和编号放入set,那么放入之前,set里剩的元素就是与之有边的元素。如果set里有右端点太小的边就删掉。显然,如果发现了超过了n-1条边,那么就直接NO。如果恰好n-1条边,再判断连通性即可。可以用并查集。

时间复杂度\(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
const int M=5e5+20,P=1e9+7;
struct Seg{
	int l,r,no;
	Seg(int a=0,int b=0,int c=0):l(a),r(b),no(c){}
	bool operator<(const Seg &x)const{
		return r<x.r;
	}
};
struct cmp{
	bool operator()(Seg a,Seg b){
		return a.l<b.l;
	}
};
struct Task{
	int n;
	Seg a[M];
	int road[M];
	inline int fnd(int x){
	    return (x==road[x])?x:road[x]=fnd(road[x]);
	}
	inline void merge(int u,int v){
	    road[fnd(u)]=fnd(v);
	}
	void init(){
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d%d",&a[i].l,&a[i].r),a[i].no=i,road[i]=i;
	}
	void run(){
		init();
		sort(a+1,a+n+1,cmp());
		set<Seg> s;
		int ans=0;
		for(int i=1;i<=n;++i){
			set<Seg>::iterator it=s.begin();
			while(it!=s.end()&&it->r<a[i].l)
				it=s.erase(it);
			while(it!=s.end()&&it->r<a[i].r){
				++ans,merge(a[i].no,it->no),++it;
				if (ans>n-1){
					printf("NO\n");
					return;
				}
			}
			s.insert(a[i]);
		}
		if (ans!=n-1){
			printf("NO\n");
			return;
		}
		unordered_set<int> st;
		for(int i=1;i<=n;++i)
			st.insert(fnd(i));
		if (st.size()==1)
			printf("YES\n");
		else
			printf("NO\n");
	}
}t;
int main(){
	t.run();
	return 0;
}

posted on 2019-12-24 00:38  diorvh  阅读(123)  评论(0编辑  收藏  举报