JOI 2017 Final题解

T1

把修改放到差分序列上做,这样每次修改只需要修改两个位置,且每次修改会对后面的所有温度产生影响

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 998244353
#define eps 1e-8
int n,m,a[200200];
ll f[200100];
ll s,t,ans;

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

void modify(int pos,ll val)
{
	if (pos>n) return;
	if (f[pos]>0) ans+=s*f[pos];
	else ans+=t*f[pos];
	f[pos]+=val;
	if (f[pos]>0) ans-=s*f[pos];
	else ans-=t*f[pos];
}

signed main()
{
	n=read();m=read();s=read();t=read();
	rep(i,0,n) a[i]=read();
	rep(i,1,n) f[i]=a[i]-a[i-1];
	ans=0;
	rep(i,1,n)
		if (f[i]>0) ans-=s*f[i];
		else ans-=t*f[i];
	while (m--)
	{
		int l=read(),r=read(),x=read();
		modify(l,x);modify(r+1,-x);
		printf("%lld\n",ans);
	}
	return 0;
}

T2

不难发现如果按照“快车->准快车->慢车”的顺序坐车一定不会更劣

对于每一段快车区间\((s_i,s_{i+1}]\),考虑如何求出最大的可到达车站

我们在\(s_i\)下车后会有一段剩余时间,记作\(rst_i\),和最开始的结论同理,我们希望在“最后一个可以使用慢车到达的车站”处建立一个准快车车站,使得从\(s_i\)可以直接到达那里,证明的话考虑往前或往后调整都不会是更优的

这样的话我们就有\(O(mk)\)个可以考虑的准快车建立点,使用优先队列贪心即可

注意一些奇奇怪怪的特判

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 998244353
#define int long long
#define eps 1e-8
int n,m,k,now[5050],s[5050];
ll a,b,c,tot,rst[5050];
priority_queue<int> q;
int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

signed main()
{
	n=read();m=read();k=read();
	scanf("%lld%lld%lld%lld",&a,&b,&c,&tot);
	rep(i,1,m) 
	{
		s[i]=read();
		rst[i]=tot-b*(s[i]-1);
		now[i]=s[i];
	}
	int ans=0;
	//rep(i,1,m) cout << now[i] << " ";cout << endl;
	if (b*(s[m]-1)>tot) ans--;
	rep(i,1,m-1)
	{
		if (rst[i]<0) continue;
		int nxt=min(s[i+1],s[i]+rst[i]/a+1);
		ans+=(nxt-s[i]);
		rst[i]-=c*(nxt-s[i]);
		now[i]=nxt;
	}
	rep(i,1,k)
	{
		rep(j,1,m-1)
		{
			if (rst[j]<0) continue;
			int nxt=min(s[j+1],now[j]+rst[j]/a+1);
			q.push(nxt-now[j]);
			rst[j]-=c*(nxt-now[j]);
			now[j]=nxt;
		}
	}
	rep(i,1,k-m)
	{
		if (q.empty()) break;
		ans+=q.top();q.pop();
	}
	printf("%lld",ans);
	return 0;
}

T3

画一下的话可以发现合法方案就是在四个角上的阶梯形,由于四种情况等价我们可以将矩阵旋转后只处理左下角的阶梯形,最后取个\(min\)即可

题目中由很明显的二分答案的暗示,但是看起来\(check\)不太容易

考虑矩阵的全局\(max\)\(min\)值,对于我们二分出来的\(mid\),我们考虑左下角的阶梯有\(\geq mid+min\),右上角的有\(\leq max-mid\),由于\(max\)\(min\)不会在同一个阶梯里,所以这样做我们一定能考虑到最优解,之后维护一个当前行的右端点指针(单调不降),每行判断一下在左下角不合法的点是否在右上角即可

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 2e9
#define eps 1e-8
int n,m,a[2020][2020],ans=maxd,mx=0,mn=maxd;

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

void reverse1()
{
	rep(i,1,n/2) rep(j,1,m) swap(a[i][j],a[n-i+1][j]);
}

void reverse2()
{
	rep(i,1,n) rep(j,1,m/2) swap(a[i][j],a[i][m-j+1]);
}

bool chk(int mid)
{
	int now=0;
	rep(i,1,n)
	{
		rep(j,1,m)
			if (a[i][j]<mx-mid) now=max(now,j);
		rep(j,1,m)
			if ((a[i][j]>mid+mn) && (j<=now)) return 0;
	}
	return 1;
}

void solve()
{
	int l=0,r=mx-mn,now=0;
	while (l<=r)
	{
		int mid=(l+r)>>1;
		if (chk(mid)) {now=mid;r=mid-1;}
		else l=mid+1;
	}
	ans=min(ans,now);
}

int main()
{
	n=read();m=read();
	rep(i,1,n) rep(j,1,m)
	{
		a[i][j]=read();
		mx=max(a[i][j],mx);
		mn=min(a[i][j],mn);
	}
	solve();
	reverse1();
	solve();
	reverse2();
	solve();
	reverse1();
	solve();
	printf("%d",ans);
	return 0;
}

T4

对于每名球员,最优策略一定是:跑到某个位置接球->带着球跑->踢出

也就是说当一个球到达某个点的时候,一定是一个距离它最近的人跑过去捡起它

接下来考虑拆点跑最短路,\((x,y,0-3)\)表示一个球在往四个方向走,\((x,y,4)\)表示有人在\((x,y)\)控球

首先球往四个方向跑的时候花费是\(a\),之后一个人在控球状态下如果将球提出可以看做\(4\)\(0-3\)连了一条长度为\(b\)的边,之后带球跑动便连\(c\)的边,最后如果一个球在某一个点要想找一个控球人的话,就是离它最近的人的距离\(\times c\),这个可以通过预先bfs实现

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 998244353
#define eps 1e-8
const int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
struct node{int to,nxt;ll cost;}sq[5000100];
int all=0,head[1501000];
struct hnode{int u;ll dis;};
bool operator <(hnode p,hnode q) {return p.dis>q.dis;}
struct pnode{int x,y;};
queue<pnode> q;
int h,w,n,id[510][510][5],a,b,c,tot=0;
ll d[510][510],dis[1500010];
bool vis[1500010],in[505][505];

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

void add(int u,int v,ll w)
{
	all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].cost=w;head[u]=all;
}

void bfs()
{
	while (!q.empty())
	{
		pnode now=q.front();q.pop();
		rep(i,0,3)
		{
			int nx=now.x+dx[i],ny=now.y+dy[i];
			if ((nx<1) || (nx>h) || (ny<1) || (ny>w)) continue;
			if (d[nx][ny]>d[now.x][now.y]+c)
			{
				d[nx][ny]=d[now.x][now.y]+c;
				q.push((pnode){nx,ny});
			}
		}
	}
}

void dij(int st)
{
	priority_queue<hnode> q;
	while (!q.empty()) q.pop();
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[st]=0;q.push((hnode){st,0});
	while (!q.empty())
	{
		int u=q.top().u;q.pop();
		if (vis[u]) continue;vis[u]=1;
		for (int i=head[u];i;i=sq[i].nxt)
		{
			int v=sq[i].to;
			if (dis[v]>dis[u]+sq[i].cost)
			{
				dis[v]=dis[u]+sq[i].cost;
				if (!vis[v]) q.push((hnode){v,dis[v]});
			}
		}
	}
}

int main()
{
	h=read()+1;w=read()+1;
	a=read();b=read();c=read();
	n=read();
	rep(i,1,h) rep(j,1,w) rep(k,0,4) id[i][j][k]=(++tot);
	//cout << tot << endl;
	int stx,sty,edx,edy;
	memset(d,0x3f,sizeof(d));
	rep(i,1,n)
	{
		int x=read()+1,y=read()+1;
		if (i==1) {stx=x;sty=y;}
		if (i==n) {edx=x;edy=y;}
		if (!in[x][y]) q.push((pnode){x,y});
		in[x][y]=1;d[x][y]=0;
	}
	bfs();
	rep(x,1,h)
		rep(y,1,w)
		{
			rep(k,0,3)
			{
				add(id[x][y][k],id[x][y][4],d[x][y]);
				add(id[x][y][4],id[x][y][k],b);
				int nx=x+dx[k],ny=y+dy[k];
				if ((nx<1) || (nx>h) || (ny<1) || (ny>w)) continue;
				add(id[x][y][k],id[nx][ny][k],a);
				add(id[x][y][4],id[nx][ny][4],c);
			}
		}
	dij(id[stx][sty][4]);
	printf("%lld\n",dis[id[edx][edy][4]]);
	return 0;
}

T5

首先显然的是我们可以先完成所有的染色,再进行折叠

将最后的序列还原,不难发现一个合法的序列是有两种颜色的连续段,且除了开头和结尾的两段长度奇偶性任意以外,其它的每一段长度均为偶数

直接做依然不大好做,注意到满足上一条件的某种颜色段的起始位置奇偶性一致,那么我们可以枚举这个奇偶性,之后调整该颜色使其合法(具体如何调整见程序),之后我们可以贪心的选取剩下最多个数的颜色作为第二种颜色。

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 998244353
#define eps 1e-8
int n,m,col[1001000],mx,cnt[1001000],cnt1[1001000];
vector<int> pos[1001000];

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

void add(int c)
{
	cnt[cnt1[c]]--;cnt1[c]++;cnt[cnt1[c]]++;
	if (cnt1[c]>mx) mx=cnt1[c];
}

void dec(int c)
{
	cnt[cnt1[c]]--;cnt1[c]--;cnt[cnt1[c]]++;
	if (!cnt[mx]) mx--;
}

int main()
{
	n=read();m=read();
	rep(i,1,n)
	{
		col[i]=read();add(col[i]);
		pos[col[i]].pb(i);
	}
	rep(i,1,m)
	{
		int len=pos[i].size();
		col[0]=col[n+1]=i;
		rep(j,0,len-1)
		{
			int now=pos[i][j];
			dec(i);
		}
		rep(j,0,len-1)
		{
			int now=pos[i][j],nxt;
			if (now&1) nxt=now-1;else nxt=now+1;
			if (col[nxt]!=i) dec(col[nxt]);
		}
		int ans=n-len-mx;
		rep(j,0,len-1)
		{
			int now=pos[i][j],nxt;
			if (now&1) nxt=now-1;else nxt=now+1;
			if (col[nxt]!=i) add(col[nxt]);
		}
		rep(j,0,len-1)
		{
			int now=pos[i][j],nxt;
			if (now&1) nxt=now+1;else nxt=now-1;
			if (col[nxt]!=i) dec(col[nxt]);
		}
		ans=min(ans,n-mx-len);
		rep(j,0,len-1)
		{
			int now=pos[i][j],nxt;
			if (now&1) nxt=now+1;else nxt=now-1;
			if (col[nxt]!=i) add(col[nxt]);
		}
		printf("%d\n",ans);
		rep(j,0,len-1)
		{
			int now=pos[i][j];
			add(i);
		}
	}
	return 0;
}
posted @ 2019-11-08 01:02  EncodeTalker  阅读(203)  评论(0编辑  收藏  举报