A - Heist
签到,输出max(ai) - min (ai) +1-n;
B - Buying a TV Set
签到,x/= gcd(x,y),y/= gcd(x,y), 答案为min(a/x,b/y);
C - Coffee Break
将n个休息时间点分到m天,每天每个时间点之间的间隔大于d,求m最小值
做法很多,思路类似。自己的乱搞的做法是从小到大排序,枚举ai,用upper_bound找大于 ai+d的第一个数,标记成和ai同一天。问题是满足ai+d 的最小的数可能已经被前面的数标记 了。由于ai + d>= ai-1 + d,可以用变量tmp保存之前标记的最后一个数的下标,如果当前查 找的数的下标小于等于tmp,则令a[tmp+1]被标记即可,并更新tmp。复杂度是O(nlogn)
或者就是用单调队列处理,过程类似,时间复杂度O(n)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int mx = 5e5+50;
struct node{
ll v;
int pos;
}a[mx];
ll b[mx];
bool cmp(node x,node y){
return x.v < y.v;
}
int vis[mx],ans[mx];
int main (){
int n,m; ll d;
cin >>n>>m>>d;
for (int i=1;i<=n;i++){
scanf ("%lld",&a[i].v);
a[i].pos=i;
}
int tmp =0;
sort (a+1,a+1+n,cmp);
for (int i=1;i<=n;i++) b[i]=a[i].v;
int tot=0;
for (int i=1;i<=n;i++){
if (!vis[i]) vis[i]=++tot;
int id=upper_bound(b+1,b+1+n,b[i]+d)-b;
if (id>n) continue;
//printf("第%d个数 %d %d\n",i,id,a[id].v);
if (id>tmp){
tmp = id;
vis[tmp]=vis[i];
}
else {
vis[++tmp]=vis[i];
}
}
printf("%d\n",tot);
for (int i=1;i<=n;i++){
ans[a[i].pos] = vis[i];
}
for (int i=1;i<n;i++) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
return 0;
}
D - Glider
比较裸的前缀和加二分
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int mx=4e5+10;
struct node{
ll l,r;
}s[mx];
bool cmp(node a,node b){
return a.l <b.l;
}
ll dis[mx];
ll val[mx];
int main (){
int n; ll h;
cin >> n>>h;
for (int i=1;i<=n;i++){
scanf ("%lld %lld",&s[i].l,&s[i].r);
}
sort (s+1,s+1+n,cmp);
dis[1]=0;
val[1]=s[1].r-s[1].l;
for (int i=2;i<=n;i++){
dis[i]=s[i].l - s[i-1].r;
dis[i]+=dis[i-1];
val[i]= s[i].r- s[i].l;
val[i]+=val[i-1];
//printf("dis = %lld , val = %lld\n\n",dis[i],val[i]);
}
ll ans = 0;
for (int i=1;i<=n;i++){
int pos=lower_bound(dis+1,dis+1+n,h+dis[i])-dis;
//printf("开始于第%d段\n",i);
//printf("结束于第%d段后",pos);
ll tmp = val[pos-1]-val[i-1];
//printf("%lld\n",tmp);
ans = max (tmp,ans);
}
printf("%lld\n",ans+h);
return 0;
}
E - Tree Reconstruction
构造题,给的是删掉每条边形成的两个分开的联通块各自最大的节点的序号,让你重构这颗树。
容易想到的删掉任何一条边留下的一个联通块最大的点是n。删除连接叶节点和树的一条边,那叶节点就是给出的一个最大值。
trick:没构造思路就画画看,会有发现。
如果有一个节点多次在删边后成为一个最大值,删的这几条边会在这个节点到最大点n的一条链上。
于是想到干脆把n当作根节点,去搞这棵树。一个点x成为最大值的次数记作cnt[x]次,在这棵树上把它和根n连cnt[x]条边,然后它到根这条链上的祖先节点u应该是小于x且cnt[u]=0;
发现这些后就知道构造方法了。从遍历点i:n-1——1,如果cnt[i]>0,就遍历点j:i-1——1,找cnt[i]-1个cnt[j] = 0的点j,连一条链到根上。条件满足即可构造出这么一颗根连了很多根链的树,不满足则输出答案“NO”。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
const int mx =1e3+50;
int cnt[mx];
int a[mx],b[mx];
struct node{
int u,v;
}edge[mx];
int vis[mx];
int main (){
int n;
cin >>n;
for (int i=1;i<n;i++){
int x,y;
scanf ("%d%d",&x,&y);
cnt[x]++;cnt[y]++;
}
int tot = 0;
for (int i=n-1;i>=1;i--){
//printf("%d cnt=%d vis=%d\n",i,cnt[i],vis[i]);
if (!vis[i] && !cnt[i]){
printf("NO\n"); return 0;
}
if (vis[i]) continue;
int x = i; int num = 0;
if (cnt[x]==1){
edge[++tot].u=x; edge[tot].v=n;
continue;
}
for (int j=i-1;j>=1;j--){
if (!cnt[j]&& !vis[j]){
edge[++tot].u=x;
edge[tot].v=j;
x=j; vis[j]=1;
num ++;
}
if (num==cnt[i]-1){
edge[++tot].u=x; edge[tot].v=n; num++;
break;
}
}
if (num<cnt[i]){
printf("NO\n"); return 0;
}
}
printf("YES\n");
for (int i=1;i<=tot;i++){
printf("%d %d\n", edge[i].u,edge[i].v);
}
return 0;
}