第45届icpc昆明站题解(C,J,H,L,M)

C.cities

题目大意:
给定一个数组,对于每次操作,可以将某段相邻且相等的数变成任意数字,问最少多少次操作可以将数组中的所有数变为相同数字?
解析:
区间dp
首先观察题意,可以得出结论:
如果某段数字全部变成{x1,x2,x3。。。。xn}时可以得到最优解,那么,这个最优解的集合中,一定包含区间的左右端点。
因此,我们只需要将整个数组变为数组的右端点,即可得到答案。

在编程时,需要讨论两种情况,一种是左右端点不同,另一种是左右端点相同。

为了能将所有的数都变成区间的右端点,我们需要找到区间中所有与右端点相同的数,然后将两端区间合并即可。

#include<bits/stdc++.h>
using namespace std;
const int N=5010;
typedef long long ll;
#define pre ne
int dp[N][N];
int pre[N];
int w[N];
map<int,int>mp;
int n;
int dfs( int l,int r){
//dfs返回值为将区间中所有数变为和右端点相同的数需要的最小操作数
    if(l==r) return 0;
    if(dp[l][r]!=-1) return dp[l][r];
    int &v=dp[l][r];
    v=r-l+1-1;
    if(w[l]==w[r]){
        v=min(v,dfs(l+1,r-1)+1);
    }
    else {
        v=min(v,dfs(l+1,r)+1);
        v=min(v,dfs(l,r-1)+1);
    }
    for( int i=pre[r];i>l;i=pre[i]){
        if(w[l]==w[r]){
            v=min(dfs(l,i)+dfs(i,r),v);
        }
        else v=min(dfs(l,i)+dfs(i,r),v);
    }
    return dp[l][r];
}
int main(){
    int t;
    cin>>t;
    while(t--){
        scanf("%d",&n);
        for( int i=1;i<=n;i++){
            scanf("%d",&w[i]);
            if(w[i]==w[i-1]) i--,n--;
        }
        mp.clear();
        for( int i=1;i<=n;i++){
                pre[i]=mp[w[i]];
                mp[w[i]]=i;
        }
        memset(dp,-1,sizeof(dp));
        cout<<dfs(1,n)<<endl;
    }
    return 0;
}

J.Mr. Main and Windmills

题目大意:
平面中存在若干个点,其中有一个起点和一个终点。对于每次询问,在除起点和终点外的所有点选择一个关键点h,当从起点向终点走的过程中有多少个点的相对位置和h点发了变化,输出发生第k次变化时所处的位置。
解析:本题点的角度分布过大,而且起点和终点与windmill的拓扑关系不确定,因此不能使用极角排序,应该用每个点与起点和终点的连线求交点,再用交点和起点之间的距离排序。
具体算法是:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
const double eps = 1e-8,inf =1e7;
int n,m;
int h,k;
int jz(double x){//判断x的符号 
	if(fabs(x)<eps) return 0;
	else return x<0?-1:1;
}
struct Point {
	double x,y;
    Point(double x = 0, double y = 0):x(x), y(y){ }//构造函数
	bool operator < (Point b)  {
		return jz(x-b.x)<0 || (jz(x-b.x)==0&&jz(y-b.y)<0);
		//先按x排序,x相同按y排序
	}
	Point operator - (Point a)  {
		return Point(x-a.x,y-a.y);
	}
	Point operator/(double k) {
		return Point(x/k,y/k);
	}
};
Point be,en;
Point p[N];
typedef Point Vector;
double Cross ( Point a,Point b){
	return a.x*b.y-a.y*b.x;
}
double Dot( Point a,Point b){
	return a.x*b.x+a.y*b.y;
}
int Point_line_relation( Point a,Point b,Point c){
	//判断a和直线bc的关系
	int t=jz(Cross(a-b,c-b));
	return t;
}
Point Cross_Point ( Point a,Point b,Point c,Point d){
	double s1=Cross(b-a,c-a);
	double s2= Cross(b-a,d-a);
	return Point (c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
}

int compare(Point b,Point c){ 
	return fabs(be.x-b.x)<fabs(be.x-c.x);
}
Point solve ( int h,int k){//第k次passh
	vector<Point>v;
	for( int i=1;i<=n;i++){
		if(i==h) continue;
		if(Point_line_relation(p[i],p[h],be)==1&&Point_line_relation(p[i],p[h],en)==-1)
		v.push_back(Cross_Point(be,en,p[i],p[h]));
		else if(Point_line_relation(p[i],p[h],be)==-1&&Point_line_relation(p[i],p[h],en)==1)
		v.push_back(Cross_Point(be,en,p[i],p[h]));
	}
	sort(v.begin(),v.end(),compare);
	int cnt=0;
	for( int i=0;i<v.size();i++){
		cnt++;
		if(cnt==k){
			return Cross_Point(be,en,p[h],v[i]);
		}
	}
	return Point(inf,inf);
}

int main(){
	cin>>n>>m;
	for( int i=1;i<=1;i++){
		double x,y;
		scanf("%lf%lf",&x,&y);
		be=Point(x,y);
		scanf("%lf%lf",&x,&y);
		en=Point(x,y);
	}
	for( int i=1;i<=n;i++){
		double x,y;
		scanf("%lf%lf",&x,&y);
		p[i]=Point(x,y);
	}
	for( int i=1;i<=m;i++){
		scanf("%d%d",&h,&k);
		Point ans=solve( h,k);
		if(jz(ans.x-inf)==0) printf("-1\n");
		else {
            if(jz(ans.x)==0) printf("0 ");
            else printf("%.7lf ",ans.x);
            if(jz(ans.y)==0) printf("0\n");
            else printf("%.7lf\n",ans.y);
			//注意-0
		}
	}
	
	
}

H.Hard Calculation

签到题

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;
	cin>>n;
	cout<<n+2020<<endl;
}

L.Simone and Graph Coloring

在这里插入图片描述
题目大意:
给出一个数组,每个逆序对之间都有一条路径相连,现在要将所有点涂色,相连的点不能涂成相同颜色,输出一种颜色数量最少的涂色方案。
解析:
上升子序列问题,使用一个堆维护所有的上升序列的最大值,同一个上升序列涂成相同的颜色,每当遇到一个新的数值,就将这个数值替换上升序列的末尾,或者创建一个新的上升序列。

#include<bits/stdc++.h>
using namespace std;
const int N=1010100,inf=0x3f3f3f3f;
int a[N];
int color[N];
set<int>se;
int main(){
	int t;
	cin>>t;
	while(t--){
		int n,cnt=0;
		scanf("%d",&n);
		se.clear();
		se.insert(inf);
		se.insert(-inf);
		for( int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		for( int i=1;i<=n;i++){
			if(a[i]<*++se.begin()){
				color[a[i]]=++cnt; 
				se.insert(a[i]);
			} 
			else {
				set<int> :: iterator it=se.upper_bound(a[i]);
				--it;
				color[a[i]]=color[*it];
				se.erase(it);
				se.insert(a[i]);
			}
		}
        printf("%d\n",cnt);
		for( int i=1;i<=n;i++){
			printf("%d ",color[a[i]]);
		}
		printf("\n");

	}
}

M.Stone games

可持久化线段树
本题需要求的是区间的答案,并且需要求解小于某个数的数字和,所以使用可持久化线段树。
这也可以理解为线段树套权值线段树
权值线段树的解题思路是:
当给出一个区间后,在这个区间 [ l , r ] 中,假设已经知道可以组成[ 1 , x ]中的所有数,这时,只需要查看[ 1 , x+1 ]这个区间的的和有没有增加,如果没有增加,说明没有x+1这个数,也就是无法组成x+1,输出答案;如果有增加,那么下次查找的区间至少是[ 1 , 2x+1 ],这是一个倍增的区间,时间复杂度为log级。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1000200;
const ll M=1e9+20;
struct node {
	ll l,r;
	ll val;
};
ll idx;
node tr[N*40];
ll h[N];
void pushup( ll rt){
	tr[rt].val=tr[tr[rt].l].val+tr[tr[rt].r].val;
}
ll insert(ll o,ll l,ll r,ll val){
	ll rt=++idx;
	tr[rt]=tr[o];
	if(l==r){
		tr[rt].val+=val;
		return rt;
	}
	ll mid=(l+r)/2;
	if(val<=mid){
		tr[rt].l=insert(tr[rt].l,l,mid,val);
	}	
	else tr[rt].r=insert(tr[rt].r,mid+1,r,val);
	pushup(rt);
	return rt;
}
ll qq( ll rl,ll rr,ll l,ll r,ll L,ll R){
	if(l>R||r<L) return 0ll;
	if(l==r) return tr[rr].val-tr[rl].val;
	if(l>=L&&r<=R) return tr[rr].val-tr[rl].val;
	ll mid=(l+r)/2;
	ll res=0;
	if(mid>=L) res+=qq(tr[rl].l,tr[rr].l,l,mid,L,R);
	if(R>mid) res+=qq(tr[rl].r,tr[rr].r,mid+1,r,L,R);
	return res;
}

int main(){
	ll n,q;
	cin>>n>>q;
	for( ll i=1;i<=n;i++){
		ll val;
		scanf("%lld",&val);
		h[i]=insert(h[i-1],1,M,val);
	}
	ll ans=0;
	for( ll i=1;i<=q;i++){
		ll l,r;
		scanf("%lld%lld",&l,&r);
		l=(l+ans)%n+1;
		r=(r+ans)%n+1;
        if(l>r)swap(l,r);
		ans=0;
		ll bound=0;
		while(1){
			bound=qq(h[l-1],h[r],1,M,1,bound+1);
			if(ans==bound) break;
			else ans=bound;
		}
        ans++;
		printf("%lld\n",ans);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值