2018-2019 ACM-ICPC, NEERC, Southern Subregional Contest, Qualification Stage

Solved


Solution


Problem A. Coffee Break
  • 贪心,对需求时间进行排序。
  • 使用优先队列取出当前所有天的最晚时刻的最小值,如果没有一天是满足条件的,则在重新开一天,否则加入那一天即可。
    代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;
struct node{
	ll a;
	int id;
}s[maxn];
bool cmp1(node x,node y)
{
	return x.a<y.a;
}
ll ans[maxn];
typedef pair<ll,ll> pre;
struct cmp{
	bool operator ()(const pre p1,const pre p2){
		return p1.second>p2.second; 
	}
};

ll num[maxn];
int main(){
	priority_queue< pre,vector<pre>,cmp >q;
	ll n,m,d;
	cin>>n>>m>>d;
	for(int i=1;i<=n;i++){
		scanf("%lld",&s[i].a);
		s[i].id=i;
	}
	sort(s+1,s+n+1,cmp1);
	q.push(make_pair(1,s[1].a));
	ans[s[1].id]=1;
	int day=1;
	for(int i=2;i<=n;i++){
		//cout<<q.top().first<<endl;
		if(q.top().second+d+1>s[i].a){
			day++;
			q.push(make_pair(day,s[i].a));
			ans[s[i].id]=day;
		}else{
			int dd=q.top().first;
			q.pop();
			ans[s[i].id]=dd;
			q.push(make_pair(dd,s[i].a));
		}
	}
	 
	printf("%d\n",day);
	for(int i=1;i<=n;i++){
		printf("%lld%c",ans[i]," \n"[i==n]);
	}
}

Problem B. Glider
  • 为了使飞机平滑的时间尽量多,那么每次飞机的起点一定是某个可平滑的线段的 \(l\) 段,高度一定是最大高度。
  • 然后我们就可以通过枚举飞机的起点,去求出飞机的飞行距离。
  • 由于是取一些平滑线段,想到可以用取尺法。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;

ll l[maxn],r[maxn];
map<ll,ll>sum;
int main()
{
	int n;
	ll h;
	scanf("%d %lld",&n,&h);
	for(int i=1;i<=n;i++){
		scanf("%lld %lld",&l[i],&r[i]);
		l[i]+=1e9;
		r[i]+=1e9; 
	}
	sum[l[1]]=0;
	sum[r[1]]=0;
	for(int i=2;i<=n;i++){
		sum[l[i]]=-(l[i]-r[i-1]);
		sum[r[i]]=sum[l[i]];
        //cout<<"xx"<<sum[l[i]]<<" "<<sum[r[i]]<<endl;
	}
	ll l1=0,r1=0;
	ll ans=0;
	ll sumx=h;
	ll num=0;
	for(l1=1,r1=1;l1<=n;l1++){
		if(l1>1){
			sumx+=(l[l1]-r[l1-1]);
			//num+=r[r1]-l[r1-1];
			num=r[r1]-l[l1];
		}
		//cout<<"xx"<<sumx<<endl;
		while(r1<n&&sumx>0){
            r1++;
			if(sumx+sum[l[r1]]<=0){
                r1--;
				//cout<<sumx<<" "<<num<<endl;
				ans=max(ans,num+sumx);
				//cout<<l1<<" "<<r1<<" "<<ans<<endl;
				break;
			}
			num=r[r1]-l[l1];
            //cout<<l1<<" "<<num<<endl;
			sumx+=sum[l[r1]];
			//cout<<"xx"<<sum[r[r1]]<<endl;
		}
		//cout<<"xx"<<num<<endl;
		//cout<<l1<<" "<<r1<<endl; 
		if(sumx>0){
            if(n==1){
                ans=max(ans,r[1]-l[1]+sumx);
            }
			//cout<<sumx<<" "<<num<<endl;
			ans=max(ans,num+sumx);
		    //cout<<l1<<" "<<r1<<" "<<ans<<endl;
		}
	}
	cout<<ans<<endl; 
}
Problem C. Bacteria
  • 暴力解决或者找规律即可。

代码:

ll ok(ll x){
	ll y=1;
	while(1){
		//cout<<y<<endl;
		if(x>=y){
			y*=2;
		}
		else{
			break;
		}
	}
	return y/2;
}
ll a[200005];
void run()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	sort(a+1,a+1+n);
	int pp = a[1];
	for(int i=1;i<=n;i++)
	{
		int xx = a[i]/pp;
		if(a[i]%a[1]!=0)
		{
			puts("-1");
			return;
		}
		while(xx%2==0)
		{
			xx/=2;
		}
		//cout<<xx<<endl;
		if(xx!=1)
		{
			puts("-1");
			return;
		}
		a[i] = a[i]/pp;
	}
	ll sum = 0;
	for(int i=1;i<=n;i++)
	{
		sum+=a[i];
	}
	ll xx = 1;
	while(xx<sum)
	{
		xx*=2;
	}
	ll ans = xx - sum;
//	cout<<ans<<endl;
	ll yy=xx/2;
	ll num=0;
	while(ans>1){
		//cout<<ok(ans)<<endl;
		ans-=ok(ans);
		//cout<<ans<<endl;
		num++;
	}
//	cout<<num<<endl;
	if(ans==1) num++;
	cout<<num<<endl;
 } 
signed main()
{
    //std::ios::sync_with_stdio(false);
    run();
 } 
Problem D. Masquerade strikes back
  • 对于某个数,每次找到一组两两不相等就代表这个数有两对可以符合。
  • 那么每次只需要通过暴力寻找可以整除某个数的数。
  • 复杂度 \(O(n\times sqrt(10000000))\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;

struct node{
    int x,y;
}ans[maxn];
int num1[10000000+10];
int num2[10000000+10];
int c[maxn];
int n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
        num1[c[i]]=1;
    }
    int k=1;
    for(int i=1;i<=n;i++){
        if(num2[c[i]]>0){
            ans[i].x=num2[c[i]];
            ans[i].y=c[i]/num2[c[i]];
            if(num2[c[i]]*num2[c[i]]!=c[i]&&num2[c[i]]<c[i]/num2[c[i]]){
                num2[c[i]]=c[i]/num2[c[i]];
            }else{
                num2[c[i]]=0;
            }
        }else{
            while(1){
                if(num1[c[i]]*num1[c[i]]>c[i]){
                    k=0;
                    break;
                }else{
                    if(c[i]%num1[c[i]]==0){
                        num2[c[i]]=num1[c[i]];
                        num1[c[i]]++;
                        break;
                    }else{
                        num1[c[i]]++;
                    }
                }
            }
            if(k==0)break;
            i--;
            continue;
        }
    }
    if(k){
        printf("YES\n");
        for(int i=1;i<=n;i++){
            printf("%d %d\n",ans[i].x,ans[i].y);
        }
    }else{
        printf("NO\n");
    }
}
Problem E. Painting the Fence
  • 首先我们用 \(set\) 维护某个颜色的位置。
  • 当我们需要操作某个颜色是,那么我们需要涂色的区域就是 \(set\) 的最大值和最小值,即 \(*st.begin()\) ~ \(*st.rbegin()\)
  • 我们枚举这个区间,如果发现这个区间内出现了被操作过的区间,那么我们就可以直接跳到这个被操作过区间的末尾即可。否则就把原本这位置上的颜色的 \(set\) 删除这个位置。
  • 在染色完以后标记已经染过的颜色即可。
  • 每个点最多被访问两次,复杂度 \(O(nlog(n))\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+10;

set<int>st[maxn];
int col[maxn];
int n,m;
int ans[maxn];
int vis[maxn];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&col[i]);
        st[col[i]].insert(i);
    }
    cin>>m;
    for(int i=1;i<=m;i++){
        int temp;
        scanf("%d",&temp);
        if(st[temp].size()<2||vis[temp]){
            vis[temp]=1;
            continue;
        }
        int l=*(st[temp].begin()),r=*(st[temp].rbegin());
        for(int j=l+1;j<r;j++){
            st[col[j]].erase(j);
            if(vis[col[j]]&&st[col[j]].size()>=1){
                j=*(st[col[j]].rbegin());
                st[col[j]].erase(j);
            }
        }
        vis[temp]=1;
    }
    for(int i=1;i<=maxn-5;i++){
        if(st[i].size()!=0&&vis[i])
        for(int j=*(st[i].begin());j<=*(st[i].rbegin());j++){
            col[j]=i;
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d%c",col[i]," \n"[i==n]);
    }
}
Problem F. Tickets
  • 枚举 \(0\) ~ \(999999\) 求出前三位和减去后三位和的绝对值,然后每次记录这些绝对值,然后遍历到哪个数,就可以求出对于那个数的答案。

代码:

int sum(int num)
{
	int x1=0,x2=0,pos=1;
	while(num>0){
		int x=num%10;
		num/=10;
		if(pos<=3){
			x1+=x; 
		}else{
			x2+=x;
		}
		pos++;
	}
	return abs(x2-x1);
}
int a[maxn];
int p[maxn];
void init()
{
	for(int i=0;i<=999999;i++){
		for(int j=0;j<sum(i);j++){
			p[i]+=a[j];
		}
		a[sum(i)]++;	
	}
}
void run()
{
	init();
	int num;
	int t = rd();
	while(t--)
	{
		scanf("%d",&num);
		printf("%d\n",p[num]); 
	}
	
 } 
signed main()
{
    //std::ios::sync_with_stdio(false);
    run();
 } 
Problem G. Tree Reconstruction
  • 首先考虑权值最大的点,对于每条边,一定有一个值等于 \(n\)
  • 考虑构成一条链。
  • 将每条边的值变成前面小、后面大的,后面大值可以保证一定是 \(n\)
  • 然后将每条边按小权值从大到小排序,从前往后遍历。
  • 如果遇到当前权值被使用了,就去找一个没有被使用的较小的权值,否则就没有答案。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+10;

struct node{
    int l,r;
}s[1005];
bool cmp(node a,node b)
{
    return a.l<b.l;
}
int have[1005];
vector<int>v;
int main()
{
    int n;
    cin>>n;
    int k=1;
    for(int i=1;i<n;i++){
        scanf("%d%d",&s[i].l,&s[i].r);
        if(s[i].l>s[i].r)swap(s[i].l,s[i].r);
        if(max(s[i].l,s[i].r)!=n){
            k=0;
        }
    }
    sort(s+1,s+n,cmp);
    if(k==0){
        printf("NO\n");
        return 0;
    }
    int maxx;
    for(int i=1;i<n;i++){
        if(have[s[i].l]==0){
            v.push_back(s[i].l);
            have[s[i].l]++;
        }else{
            for(int j=1;j<=n;j++){
                if(have[j]==0){
                    if(j>s[i].l){
                        k=0;
                        break;
                    }
                    v.push_back(j);
                    have[j]++;
                    break;
                }
            }
        }
        if(k==0)break;
    }
    v.push_back(n);
    have[n]++;
    for(int i=1;i<=n;i++){
        if(have[i]==0||have[i]>1){
            k=0;
            break;
        }
    }
    if(k==0||v.size()!=n){
        printf("NO\n");
    }else{
        printf("YES\n");
        for(int i=0;i<n-1;i++){
            printf("%d %d\n",v[i],v[i+1]);
        }
    }
}
Problem H. Theater Square
  • 考虑部分被覆盖的那几行中,前面部分和后面部分奇偶性,以及列数的奇偶性得出答案。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;

int main()
{
	int n,m,sum=0;
	int x1,y1,x2,y2;
	cin>>n>>m;
	cin>>x1>>y1>>x2>>y2;
	int d=(x2-x1+1);
	if(y1%2==0)sum+=d;
	if((m-y2)%2==1)sum+=d;
	if(m%2==1){
		sum+=(n-d);
	} 
	printf("%d\n",sum/2+(sum%2));
}
Problem J. Buying a TV Set
  • 先约分,然后取比例最小的得到答案即可。
  • 队友的二分答案也可以。
void run()
{
	ll a,b,x,y;
	cin>>a>>b>>x>>y;
	ll ans = __gcd(x,y);
	ll xx = x/ans;
	ll yy = y/ans;
	ll maxx1 = a/xx,maxx2 = b/yy;
	if(min(maxx1,maxx2) == 0)
	{
		puts("0");
		return;
	}
	ll left = 0,right = min(maxx1,maxx2);
	ll maxx = 0;
	while(left <= right)
	{
		ll mid = (left + right)>>1;
		if(xx*mid<=a&&yy*mid<=b)
		{
			left = mid + 1;
			maxx = max(maxx,mid);
		}
		else
		{
			right = mid - 1; 
		} 
	}
	cout<<maxx<<endl;
 } 
signed main()
{
    //std::ios::sync_with_stdio(false);
    run();
 } 
Problem K. Medians and Partition
  • 考虑满足条件的子数组一定是大于等于 \(m\) 的数比小于 \(m\) 的数至少多一个。
  • 那么我们求出大于等于 \(m\) 的数为 \(a\)个,小于 \(m\) 的数为 \(b\) 个,那么答案就是 \(max(0,a-b)\)

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,m,k;
	scanf("%d%d ",&n,&m);
	int a=0,b=0;
	while(n--){
		scanf("%d",&k);
		if(k>=m) a++;
		else b++;
	}
	printf("%d\n",max(a-b,0));
}
Problem L. Ray in the tube
  • 考虑暴力,对每种距离进行枚举,然后求出答案。但这样暴力一定超时。
  • 考虑每种距离一定由 \(2^n\) \((0<=n<=MAXN)\)组成 ,那么枚举 \(2^n\)即可。
  • 时间复杂度 \(O(nlog(1e9))\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+10;

int x1[maxn],x2[maxn];
map<int,int>mp;
int main()
{
    int n,m,y1,y2;
    mp.clear();
    scanf("%d%d",&n,&y1);
    for(int i=1;i<=n;i++){
        scanf("%d",&x1[i]);
    }
    scanf("%d%d",&m,&y2);
    for(int i=1;i<=m;i++){
        scanf("%d",&x2[i]);
    }
    int ans=2;
    for(int d=2;d<=1e9;d*=2){
        mp.clear();
        for(int i=1;i<=n;i++){
            mp[x1[i]%d]++;
            ans=max(ans,mp[x1[i]%d]);
        }
        for(int i=1;i<=m;i++){
            mp[(x2[i]+d/2)%d]++;
            ans=max(ans,mp[(x2[i]+d/2)%d]);
        }
    }
    printf("%d\n",ans);
}

Summary
**

  • 在前期队友的题上机有错的情况下,再上去一个人一起看。
  • 在卡题时,需要有人去开别的可做的题。
  • 中期,最好由两个人去一起解一道题,整个队伍都需要明白题意,尽量减少一个人去开一道非打卡题的题目,除非很有把握。
  • 后期增强配合。

2021-03-05 team training

posted @ 2021-03-08 23:18  hachuochuo  阅读(89)  评论(0编辑  收藏  举报