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);
}
}