2020HHUACM寒假訓練第二、三週題目總結

eg:適逢春節過年的兩週,拉題的是我隊友和hhu天花板wzl學長,感覺題目質量還是很ok的(但看了第四周的題目,感覺我們真是手下留情了qaq)。題解的順序是我個人按照題目難易度的排名給出的。

J - Nearest Common Ancestors poj-1330

題意:最近公共祖先模板題。
題解:求最近公共祖先的方法太多了隨便掏出一個就能秒了。注意確定一下根節點即可。

#include "stdio.h"
#include "string.h"
#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
const int maxn=1e4+10;
vector<int>G[maxn];
int n,cnt;
int head[maxn],d[maxn],in[maxn],f[maxn][31];//f[x][y]表示x向上i跳2^y步的祖先節點
void dfs(int pos,int fa){
    d[pos]=d[fa]+1;//d代表深度
    f[pos][0]=fa;
    for(int i=0;i<=19;i++)
        f[pos][i+1]=f[f[pos][i]][i];
    for(int i=0;i<G[pos].size();i++){
        int u=G[pos][i];
        if(u==fa)continue;
        //dis[u]=dis[pos]+edge[i].v; 
        f[u][0]=pos;
        dfs(u,pos);
    }
}//dfs預處理f數組
int LCA(int x,int y){
    if(d[x]<d[y])swap(x,y);
    for(int i=20;i>=0;i--){
        if(d[f[x][i]]>=d[y])x=f[x][i];
        if(x==y)return x;
    }//保持x和y在同一深度
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        memset(in,0,sizeof(in));
        memset(f,0,sizeof(f));
        memset(d,0,sizeof(d));d[0]=-1;
        for(int i=1;i<=n;i++)G[i].clear();
        for(int i=1;i<n;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            G[x].push_back(y);
            f[y][0]=x; in[y]++;
        }
        int node;
        for(int i=1;i<=n;i++)
        	if(!in[i]){
        		node=i;
        		break;
			}
        dfs(node,0);
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%d\n",LCA(u,v));
    }
    return 0;
}

F - How I Mathematician Wonder What You Are! poj - 3130

題意:問多邊形的核是否存在。
題解:半平面交模板題,直接套模板即可。詳細算法傳送門

#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream> 
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
struct Point{
	double x,y;
};
Point p[maxn],a[maxn],b[maxn],t[maxn];
int n,m;
double A,B,C;
void GetLine(Point a,Point b){//得直線 
	A=b.y-a.y;
	B=a.x-b.x;
	C=b.x*a.y-a.x*b.y;
}
Point interpoint(Point a,Point b){//兩直線求交點 
	double x=fabs(A*a.x+B*a.y+C);
	double y=fabs(A*b.x+B*b.y+C);
	Point ans;
	ans.x=(a.x*y+b.x*x)/(x+y);
	ans.y=(a.y*y+b.y*x)/(x+y);
	return ans;
}
void cut(){
	int cnt=0;
	for(int i=1;i<=m;i++){
		if(A*a[i].x+B*a[i].y+C>=0)b[++cnt]=a[i];
		else{
			if(A*a[i-1].x+B*a[i-1].y+C>0)b[++cnt]=interpoint(a[i-1],a[i]);
			if(A*a[i+1].x+B*a[i+1].y+C>0)b[++cnt]=interpoint(a[i],a[i+1]);
		}
	}
	for(int i=1;i<=cnt;i++)a[i]=b[i];
	a[0]=b[cnt];a[cnt+1]=b[1];
	m=cnt;
}
void solve(){
	for(int i=1;i<=n;i++)a[i]=p[i];
	p[n+1]=p[1];a[n+1]=p[1];a[0]=p[n];
	m=n;
	for(int i=1;i<=n;i++){
		GetLine(p[i],p[i+1]);
		cut();
	}
}
void init(){//將點變爲逆時針 
	for(int i=1;i<=n;i++)t[i]=p[n-i+1];
	for(int i=1;i<=n;i++)p[i]=t[i];
}
int main(){
	while(~scanf("%d",&n)){
		if(!n)break;
		for(int i=1;i<=n;i++){
			double x,y;
			scanf("%lf%lf",&x,&y);
			p[i].x=x,p[i].y=y;
		}
		init();
		solve();
		if(m)puts("1");
		else puts("0");
	}
	return 0;
}

B - Dr. Evil Underscores CodeForces - 1285D

題意:給出n個數,找出一個數x使得max(aix)max(ai⊕x)最小,問這個最小值是多少。
題解:一道很好的字典樹上dp的題目。將n個數轉換成二進制全部排列下來,一位一位進行考慮。若所有的二進制數該位都爲0或者都爲1,那麼最終答案的這位肯定是0。如果不同,那麼最終答案的這位必然爲1,填0或者填1就取min就好。再轉換爲十進制即可。看代碼比較好理解一點。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
ll read(){
    ll f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
int n;
vector<int>q;
int solve(vector<int>q,int k){//q即爲數組 k即爲第幾位(二進制下) 
	if(q.size()==0 || k<0)return 0; 
	vector<int>l,r;
	for(int i=0;i<q.size();i++){
		if((q[i]>>k)&1)//q[i]的第k位爲0 
			l.push_back(q[i]); 
		else //q[i]的第k位爲1 
			r.push_back(q[i]);
	}
	if(l.size()==0)return solve(r,k-1);//全0 
	if(r.size()==0)return solve(l,k-1);//全1 
	return (1<<k)+min(solve(l,k-1),solve(r,k-1));
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		q.push_back(x);
	} 
	printf("%d\n",solve(q,30));
	return 0;
}

C - Path Queries CodeForces - 1213G

題意:某場div3的最後一題,給出一個有邊權的樹,接下來給出一個m個詢問,每個詢問有個qi,問樹上長度不超過qi的點對有多少個。
題解:顯然,如果長度超過qi那麼該邊就是沒有任何意義的。所以,將詢問離線,逐個加邊。如果這麼加邊,一個聯通塊對答案的貢獻就是size(size1)/2size*(size-1)/2。之後運用並查集維護不同聯通塊之間的關係即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
ll read(){
    ll f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
struct arr{
	int x,y,z;
}p[maxn];
struct node{
	int val,no;
}a[maxn];
int n,m;
int f[maxn];
ll ans[maxn],s[maxn];
ll sum;
bool cmp1(arr a,arr b){
	return a.z<b.z;
}
bool cmp2(node a,node b){
	return a.val<b.val;
}
int find(int x){
	if(x==f[x])return x;
	return f[x]=find(f[x]);
}
void unite(int a,int b){
	int fa=find(a),fb=find(b);
	if(fa!=fb){
		sum-=s[fa]*(s[fa]-1)/2;
		sum-=s[fb]*(s[fb]-1)/2;
		f[fb]=fa;
		s[fa]+=s[fb];
		sum+=s[fa]*(s[fa]-1)/2;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
		scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
	for(int i=1;i<=n;i++)s[i]=1,f[i]=i;
	for(int i=1;i<=m;i++)scanf("%d",&a[i].val),a[i].no=i;
	sort(p+1,p+n,cmp1);
	sort(a+1,a+1+m,cmp2);
	int pos=1;
	for(int i=1;i<=m;i++){
		while(p[pos].z<=a[i].val && pos<n){
			unite(p[pos].x,p[pos].y);
			pos++; 
		} 
		ans[a[i].no]=sum;
	}
	for(int i=1;i<m;i++)printf("%lld ",ans[i]);
	printf("%lld\n",ans[m]);
	return 0;
}

E - Segments poj - 3304

題意:t組數據,每組給出n個線段,如果他們投影到同一條直線上,他們的投影是否相交於至少一個點。是則yes否則no。
題解:如果存在這樣的直線,過投影相交區域作直線的垂線,該垂線必定與每條線段相交。可以發現就是是否存在一條直線與所有線段相交。坑點就是隻有一條線段的時候答案是yes。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<math.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
ll read(){
    ll f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
struct arr{
	double s1,e1,s2,e2;
}seg[110];
int n;
double dis(double a,double b,double c,double d){
	return sqrt((a-c)*(a-c)+(b-d)*(b-d));
}
double segcross(double a1,double b1,double a2,double b2,double a,double b){
	return (a2-a1)*(b-b1)-(a-a1)*(b2-b1);
}
int check(double a1,double b1,double a2,double b2){
	if(dis(a1,b1,a2,b2)<eps)return 0;
	for(int i=1;i<=n;i++)
		if(segcross(a1,b1,a2,b2,seg[i].s1,seg[i].e1)*segcross(a1,b1,a2,b2,seg[i].s2,seg[i].e2)>eps)return 0;
	return 1; 
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%lf%lf%lf%lf",&seg[i].s1,&seg[i].e1,&seg[i].s2,&seg[i].e2);
		int flag=0;
		for(int i=1;i<n;i++){
			for(int j=i+1;j<=n;j++){
				if(check(seg[i].s1,seg[i].e1,seg[j].s1,seg[j].e1))flag=1;
				if(check(seg[i].s1,seg[i].e1,seg[j].s2,seg[j].e2))flag=1;
				if(check(seg[i].s2,seg[i].e2,seg[j].s1,seg[j].e1))flag=1;
				if(check(seg[i].s2,seg[i].e2,seg[j].s2,seg[j].e2))flag=1;
				if(flag)break;
			}
			if(flag)break;
		}
		if(n==1)flag=1;
		if(!flag)puts("No!");
		else puts("Yes!");
	}
	return 0;
}

A - Can you answer these queries V spoj - GSS5

題意:給出一個長度爲n的數組,m個詢問,每個詢問有四個整數x1,y1,x2,y2。令l[x1,y1],r[x2,y2]l\in[x1,y1],r\in[x2,y2],求出最大的lirai\sum_{l\leq i\leq r}ai
題解
很玄妙的一道線段樹題。分兩大種情況
1:相交
按照答案的左右區間所在位置分類討論
共四種
1)答案區間在相交部分 區間和
2)左不在右在 左最大後綴+右最大前綴
3)左在右不在 同2)
4)左右都不在 同2
2:不交
左最大後綴+右最大前綴+左右間的區間和
具體實現和解決看代碼部分就可以了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+10;
const int mod=1e9+7;
const ll INF=1e11;
ll read(){
    ll f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
struct arr{
	ll lmx,rmx,sum,num;//lmx前綴 rmx後綴 
}s[maxn*4],nul;
ll a[maxn];
int n;
arr pushup(arr a,arr b){	
	arr t;
	t.sum=a.sum+b.sum;
    t.lmx=max(a.sum+b.lmx,a.lmx);
    t.rmx=max(b.sum+a.rmx,b.rmx);
    t.num=max(a.num,max(b.num,a.rmx+b.lmx));
	return t;
}
void build(int node,int l,int r){
	int mid=(l+r)>>1;
	if(l==r){
		s[node].sum=a[l];
		s[node].lmx=a[l];
		s[node].rmx=a[l];
		s[node].num=a[l];
		return;
	}
	build(node<<1,l,mid);
	build(node<<1|1,mid+1,r);
	s[node]=pushup(s[node<<1],s[node<<1|1]);
}
arr query(int L,int R,int l,int r,int node){
	if(L>R)return nul;
	int mid=(l+r)>>1;
	if(L<=l && r<=R)return s[node];
	if(R<=mid)return query(L,R,l,mid,node<<1);
	if(L>mid)return query(L,R,mid+1,r,node<<1|1);
	return pushup(query(L,R,l,mid,node<<1),query(L,R,mid+1,r,node<<1|1));
}
ll solve(int s1,int e1,int s2,int e2){
	ll ans=0;
	if(e1<s2){
		ans+=query(e1,s2,1,n,1).sum;
		ans+=max(1ll*0,query(s1,e1-1,1,n,1).rmx);
		ans+=max(1ll*0,query(s2+1,e2,1,n,1).lmx);
	}else{
		arr a1=query(s1,s2-1,1,n,1);
		arr a2=query(s2,e1,1,n,1);
		arr a3=query(e1+1,e2,1,n,1);
		ans=max(a2.num,a1.rmx+a2.lmx);
		ans=max(ans,a2.rmx+a3.lmx);
		ans=max(ans,a1.rmx+a2.sum+a3.lmx);
	}
	return ans;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
		build(1,1,n);
		int q;
		scanf("%d",&q);
		while(q--){
			int st1,ed1,st2,ed2;
			scanf("%d%d%d%d",&st1,&ed1,&st2,&ed2);
			printf("%lld\n",solve(st1,ed1,st2,ed2));
		}
	}
	return 0;
}

D - Lomsat gelral CodeForces - 600E

題意:給出一棵樹,每個節點有一個顏色,求每個節點的子樹中顏色數目最多的顏色的和。
題解:dsu on tree的入門模板題。關於dsu on tree的優質博客傳送門

#include "stdio.h"
#include "string.h"
#include "iostream"
#include "algorithm"
#include "vector"
#include "math.h"
#include "map"
#include "set"
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int INF=0x3f3f3f;
const int mod=1e9+7;
int n,cnt;
int a[maxn],head[maxn],sz[maxn],son[maxn],f[maxn];
ll ans[maxn],num,sum,tot;
struct arr{
    int to,next;
}edge[maxn*2];
void addedge(int u,int v){
    cnt++;
    edge[cnt].next=head[u];edge[cnt].to=v;
    head[u]=cnt;
}
void dfs1(int x,int fa){//輕重鏈劃分
    sz[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(to==fa)continue;
        dfs1(to,x);
        sz[x]+=sz[to];
        if(sz[to]>sz[son[x]])son[x]=to;
    }
}
void add(int x,int fa,int val){
    f[a[x]]+=val;//
    if(f[a[x]]>tot)tot=f[a[x]],sum=a[x];
    else if(f[a[x]]==tot)sum+=1ll*a[x];
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(to==fa || to==num)continue;
        add(to,x,val);
    }
}
void dfs2(int x,int fa,int opt){
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(to==fa)continue;
        if(to!=son[x])dfs2(to,x,0);//暴力輕邊貢獻
    }
    if(son[x])dfs2(son[x],x,1),num=son[x];//統計重兒子,不消除影響
    add(x,fa,1);num=0;//暴力統計輕兒子
    ans[x]=sum;//更新答案
    if(!opt)add(x,fa,-1),sum=0,tot=0;//刪除貢獻
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    dfs1(1,0);
    dfs2(1,0,0);
    for(int i=1;i<n;i++)printf("%lld ",ans[i]);
    printf("%lld\n",ans[n]);
    return 0;
}

總結

摸魚還是香啊,寒假訓練的題目都是以模板題爲主不斷學習新算法鞏固入門算法纔是正確姿勢!剩下的三道題目分別是斜率優化dp入門題,帶修莫隊,最小費用最大流,如果有時間補了就回來填坑。orz

發佈了8 篇原創文章 · 獲贊 8 · 訪問量 1577
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章