ICPC Mid-Central USA Region 2019

b - Convoy
  • 首先根据贪心来说,肯定让更熟悉城市的人来开车,即 t i t_i ti小的人,所以先对所有的时间进行排序
  • 然后再来考虑怎么求答案的问题,由于每个人的 t i t_i ti都有可能不同,所以说有时候一个人跑完两圈了,另外一个人可能一圈都没有跑完,所以直接求答案可能会有一定的难度.
  • 考虑二分出需要多少时间 ( x ) (x) (x),然后每次来 c h e c k check check一下这个时间行不行,用 x / a [ i ] x/a[i] x/a[i]得到可以走几趟,单数趟和双数趟是不一样的,因为单数是到了体育场,双数是家, a [ i ] a[i] a[i]只用枚举前 i i i个就行了
attention: k k k有可能会大于 n n n,这种情况让 k = n k=n k=n
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 100000
#define int long long
using namespace std;
int n,k,a[maxn];
bool check(int x){
	int cnt=0;
	for(int i=1;i<=k;i++){
		if(a[i]>x) break;
		int temp=x/a[i];
		cnt+=(temp/2+temp%2)*4+1;
	}
	return cnt>=n;
}
signed main(){
	scanf("%lld %lld",&n,&k);
	if(k>=n) k=n;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	sort(a+1,a+1+n);
	int l=0,r=10000000000000,ans=r;
	while(l<=r){
		int mid=(l+r)/2;
		if(check(mid)) r=mid-1,ans=min(ans,mid);
		else l=mid+1;
	}
	printf("%lld\n",ans);
	return 0;
}

e - Dragon Ball I
  • 其实题目很简单,就是要走完所有有龙珠的点就行了,所以首先就会想到最短路,但是这里我们不需要任意两点之间的最短路,只用知道七个龙珠之间的距离就好了,至于有一些龙珠在相同的位置,也没关系,让他们之间的距离等于 0 0 0就行了

  • 先用 d i j k s t r a dijkstra dijkstra七次,求出七龙珠之间的距离

  • 然后是求出答案的过程,可以使用状态压缩,但是这个地方没有必要,可以直接枚举顺序,用 n e x t _ p e r m u t a t i o n next\_permutation next_permutation就行了

attention:
  • 求两个七龙珠之间的距离的时候要新开一个 D i s Dis Dis数组
  • 1 1 1出发,但不代表 1 1 1有七龙珠,所以是 1 → ? → . . . → ? 1 \to?\to...\to? 1?...?一共要走 7 7 7
  • 初始的时候用 t e m p = d i s [ a [ 1 ] ] [ 1 ] temp=dis[a[1]][1] temp=dis[a[1]][1]而不是 t e m p = D i s [ a [ 1 ] ] [ 1 ] temp=Dis[a[1]][1] temp=Dis[a[1]][1].
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define maxn 1010000
#define int long long
#define inf 1000000000000000000 
using namespace std;
int dis[10][maxn],head[maxn],pos[10],n,m,k,book[maxn],Dis[10][10];
struct node{
	int to,next,w;
}edge[maxn];
void add(int u,int v,int w){
	edge[++k]=(node){v,head[u],w}; head[u]=k;
	edge[++k]=(node){u,head[v],w}; head[v]=k;
}
struct point{
	int id,val;
	friend bool operator < (point a,point b) {return a.val>b.val;}
};
void dijkstra(int s,int now){
	priority_queue<point> q;
	for(int i=0;i<=n;i++) dis[now][i]=inf; 
	dis[now][s]=0;
	q.push((point){s,0});
	memset(book,0,sizeof book);
	while(!q.empty()){
		int u=q.top().id; q.pop();
		if(book[u]) continue; book[u]=true;
		for(int i=head[u];i;i=edge[i].next){
			int v=edge[i].to;
			if(dis[now][v]>dis[now][u]+edge[i].w){
				dis[now][v]=dis[now][u]+edge[i].w;
				q.push((point){v,dis[now][v]});
			}
		}
	}
}
signed main(){
	scanf("%lld %lld",&n,&m);
	for(int i=1,u,v,w;i<=m;i++) scanf("%lld %lld %lld",&u,&v,&w),add(u,v,w);
	for(int i=1;i<=7;i++) scanf("%lld",&pos[i]);
	for(int i=1;i<=7;i++) dijkstra(pos[i],i);
	for(int i=1;i<=7;i++)
		for(int j=i;j<=7;j++)
			Dis[i][j]=Dis[j][i]=dis[i][pos[j]];
	int ans=inf;
	int a[]={0,1,2,3,4,5,6,7};
	do{
		int temp=dis[a[1]][1];
		for(int i=2;i<=7;i++) temp+=Dis[a[i-1]][a[i]];
		ans=min(ans,temp);
	}while(next_permutation(a+1,a+8));
	if(ans>=inf) ans=-1;
	printf("%lld\n",ans);
	return 0;
}

g - Farming Mars

题意就是看 ( l , r ) (l,r) (l,r)中有没有一个数出现的次数大于区间长度的一半,这道题也有很多的解决方法,比如将这些实数离散化,然后用桶来统计.或者在这个区间之中把数字取出来,排序,再怎么怎么的.但是这样做会T,这道题有一种专门的统计方法(摩尔多数投票算法)就.直接看代码吧.

double num=a[l];
int cnt=1;
for(int j=l+1;j<=r;j++){
    if(a[j]==num) cnt++;
    else cnt--;
    if(!cnt) num=a[j],cnt=1;
}

如果一个数字出现了一半以上 ( x ) (x) (x),那么它最终一定会留下来,为什么呢,我么可以用抵消的思想来看, x x x在这种操作下一定会抵消其他所有的数字,并且还会有剩.但是这种方法会有一个问题,如果没有一个数字大于一半以上的话,最终得到的答案就不行,甚至不是出现的最多的那个数,但是我们不关心出现的是不是最多,我们只关心它出现次数是不是大于给定值,所以我们最后再 c h e c k check check一下得到的这个 x x x就行了

#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 500000
#define int long long
using namespace std;
int n,m;
double a[maxn];
signed main(){
	scanf("%lld %lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
	for(int i=1,l,r;i<=m;i++){
		scanf("%lld %lld",&l,&r);
		double num=a[l];
		int cnt=1;
		for(int j=l+1;j<=r;j++){
			if(a[j]==num) cnt++;
			else cnt--;
			if(!cnt) num=a[j],cnt=1;
		}
		cnt=0;
		for(int j=l;j<=r;j++) if(a[j]==num) cnt++;
		if(cnt>=(r-l+1)/2+1) printf("usable\n");
		else printf("unusable\n"); 
	}
	return 0;
}

i - Sum and Product

这道题暴力特别好写,就是 n 2 n^2 n2枚举就行了,关键是怎么更优,我一直在想有没有 n l o g n nlogn nlogn的做法,导致最终没有做出来,而我们因该更关心这个数列的一些性质,首先,因为要算一个区间的乘积,所以首先想到的就是这个积会很大很大,除非它等于 1 1 1,就算是最小的 2 2 2,往后面连续 63 63 63个也会爆 l o n g   l o n g long \ long long long,所以当乘积大到一种程度的时候就可以 b r e a k break break了,即乘积 > > >所有 a [ i ] a[i] a[i]之和时.然后我们把中间的 1 1 1给他直接跳过就行了,因为乘积始终不变.然后如果乘积在 s u m [ i → j ] sum[i\to j] sum[ij] s u m [ i → N e x t [ j ] − 1 ] sum[i\to Next[j]-1] sum[iNext[j]1]之间的话,中间肯定就能找到一个可以的

#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 200005
#define int long long
using namespace std;
int n,a[maxn],ans,Next[maxn],sum[maxn];
int get(int l,int r) {return sum[r]-sum[l-1];}
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
	for(int i=n,pre=n+1;i;i--){
		Next[i]=pre;
		if(a[i]>1) pre=i;
	}
	for(int i=1;i<=n;i++){
		int mul=1;
		for(int j=i;Next[j];j=Next[j]){
			mul*=a[j];
			if(mul>sum[n]) break;
			if(mul>=get(i,j) && mul<=get(i,Next[j]-1)) ans++; 
		}
	}
	printf("%lld\n",ans-n);
	return 0;
}

j - True/False Worksheet
  • 因为是要一段不同,那么这一段有一个地方不同就行了,一段相同,这一段就必须全部相同,现在我们默认在一个地方放置 1 1 1,如果需要这一段有一个不同,我们就只用选择在一个地方放一个 0 0 0就行了,就类似于贪心,我们放 0 0 0的地方越少,那么 1 1 1的地方就越多,就更能满足一段地方全部相同这个操作.
  • 我们先预处理出一个地方的最远需要达到的相同的地方,和最近需要放的一个不同的 0 0 0,注意,只是 r r r这一个点, l → r l\to r lr中间的不用管
for(int i=1;i<=n;i++) same[i]=i;
for(int i=1,l,r;i<=m;i++){
    scanf("%d %d %s",&l,&r,s);
    if(s[0]=='s') same[r]=min(same[r],l);
    else different[r]=max(different[r],l);
}
  • 转移方程和代码如下,其中 f [ i ] f[i] f[i]表示 i i i之前的的地方一共的方案数,不包括 i i i这个地方
for(int i=1;i<=n;i++){
    int Max=-inf,Min=inf;
    for(int j=i;j;j--){
        Max=max(Max,different[j]); Min=min(Min,same[j]);
        if(Min>=j && j>Max) f[i]=(f[i]+f[j-1])%mod;
    }
}
  • j ≤ M i n j\le Min jMin的时候转移也就是说在 s a m e same same的区间内都不更新,在 s a m e same same区间外更新,区间内一样就行了,如下图
  • g , f g,f g,f不更新,在 e e e更新, e e e代表的就是 e e e之前的答案,但是在 d d d点不更新,因为 d d d需要放一个不同的,就放在这个点就行了,然后前面的都不用去看了,因为答案都在 e e e点了,假如没有蓝色的线,就会在 a , d , e a,d,e a,d,e点更新,但是根据常理,就是 e e e点的答案乘 2 2 2就行了,(这样也行),但是在前面全部加一遍就等同于乘 2 2 2,因为加上了 e e e,还加上了 e e e要加的东西.但是为什么加上蓝色的线就只会在 e e e更新,因为在 e e e点的时候,是不会在 d d d点放一个 0 0 0的,就相当于少了一半的答案( d d d本来还可以放 1 1 1,所以他前面的东西就不用去看了,它前面的合起来就等于一半的答案)

图.png

#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 5010
#define mod 1000000007
using namespace std;
int n,m,same[maxn],different[maxn],f[maxn];
char s[maxn];
signed main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) same[i]=i;
	for(int i=1,l,r;i<=m;i++){
		scanf("%d %d %s",&l,&r,s);
		if(s[0]=='s') same[r]=min(same[r],l);
		else different[r]=max(different[r],l);
	}
	f[0]=2;
	for(int i=1;i<=n;i++){
		int Max=-mod,Min=mod;
		for(int j=i;j;j--){
			Max=max(Max,different[j]); Min=min(Min,same[j]);
			if(Min>=j && j>Max) f[i]=(f[i]+f[j-1])%mod;
		}
	}
	printf("%d\n",f[n]%mod);
	return 0;
}

关注+点赞😍

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

d3ac

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值