1. 程式人生 > 實用技巧 >atcoder abc 188 題解

atcoder abc 188 題解

A - Three-Point Shot

題目大意

兩個球隊現在分數分別給出,問少的一方投入三分球之後是否能翻盤.

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	


int main()
{
	int a,b;cin >> a >> b;
	if(a > b)	swap(a,b);
	if(a + 3 > b)	cout << "Yes";
	else cout << "No";
    return 0;
}

B - Orthogonality

題目大意

給定兩個向量,問兩者內積是否是0.

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;
int a[N],b[N];
int main()
{
	ll res = 0;
	int n,x,y;scanf("%d",&n);
	forn(i,1,n)	scanf("%d",&a[i]);
	forn(i,1,n)	scanf("%d",&b[i]);
	
	forn(i,1,n)	res += a[i] * b[i];
	if(res == 0)	puts("Yes");
	else	puts("No");
    return 0;
}

C - ABC Tournament

題目大意

\(2^n\)個隊伍打比賽,每個隊伍有自己的分值,分值高的獲勝.對局呈完美二叉樹形態,從低到高,問第二名是誰.

思路

把整個局面劃分兩段再遞迴,當區間裡只有一個人的時候返回自己,其他時候返回兩個人對戰勝利者.記錄最後一個對局中輸掉的人的編號,即為第二名.

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = (1 << 16) + 7;
int a[N],last;

int solve(int l,int r)
{
	if(l == r)	return l;
	int mid = l + r >> 1;
	int lf = solve(l,mid),rt = solve(mid + 1,r);
	if(a[lf] > a[rt])	return last = rt,lf;
	else	return last = lf,rt;
}

int main()
{
	int n;scanf("%d",&n);
	int m = 1 << n;
	forn(i,1,m)	scanf("%d",&a[i]);
	solve(1,m);
	printf("%d",last);
    return 0;
}

D - Snuke Prime

題目大意:

有個公司售賣他們的\(n\)種服務,售賣方式有兩種:每天花費\(C\)元訂閱費,在訂閱期間任何服務可以直接使用;對\(i\)種服務花費\(c_i\)元每天使用.現給出若干個\(a_i,b_i,c_i\)表示在第\(a_i\)天到第\(b_i\)天需要使用第\(i\)種服務,以及使用這項服務的每天花費\(c_i\)

資料範圍:

\(1 \leq n \leq 2 * 10^5\)

\(1 \leq C \leq 10^9\)

\(1 \leq a_i \leq b_i \leq 10^9\)

\(1 \leq c_i \leq 10^9\)

思路

由於資料範圍過大,但是種類數只有\(2*10^5\)考慮離散化打標記.但是離散化只保留相對大小關係,使得"天數"這一資訊被刪掉,因此離散化處理不了.

考慮掃描線,以天數為掃描線的劃分.將所有服務分成兩個事件:一個事件包含兩個變數\({a,c}\)表示在第\(a\)天,增加一個每天花費為\(c\)的服務.對於原來的每種服務拆成\(\{a_i,c_i\}\)以及\(\{b_i+1,c_i\}\)兩個事件,表示第\(a_i\)天開始增加,直到\(b_i+1\)天結束.維護當前天數\(cur\)以及當前需要使用的各種服務的總花費\(sum\),因為可以通過訂閱的方式使用所有服務,所以每段的實際貢獻是\((a - cur) * min(sum,C)\).

將所有事件按左端點排序,掃描即可.

注意防範爆資料

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
typedef pair<ll,ll> pll;
#define x first
#define y second
#define int ll
const int N = 2e5+7;
struct Node
{
	int a,b,c;
}a[N];

signed main()
{
	int n,C;scanf("%lld%lld",&n,&C);
	vector<pll> event;
	forn(i,1,n)	scanf("%lld%lld%lld",&a[i].a,&a[i].b,&a[i].c);
	forn(i,1,n)	event.push_back({a[i].a,a[i].c}),event.push_back({a[i].b + 1,-a[i].c});	
	
	sort(event.begin(),event.end());
	
	int cur = 0;ll res = 0,sum = 0;
	for(auto& _ : event)
	{
		int a = _.x,c = _.y;
		res += 1ll*(a - cur) * min(max(sum,0ll),1ll*C);
		sum += c;
		cur = a;
	}
	
	printf("%lld",res);
    return 0;
}

E - Peddler

題目大意

\(n\)\(m\)邊有向圖,保證對於任何一條有向邊\((u,v)\)都有\(u < v\),即只會從編號較小的點連向較大的點.每個點都有一個值\(A_i\)表示在這個點買入黃金的價格和賣出黃金的價格.你不能在同一個點買入並賣出,求最大的獲利是多少.

你不能不買入,即使有負利潤也必須買.

圖可能不連通

思路

很惱火的一個題.

首先這個題有一個一般的解法,可以使用取極值的方式替代單源最短路的更新,這裡不展開.

因為對於任何邊都有\(u < v\)所以這個圖是個DAG.直接\(dp\)即可.

  • 狀態:\(f_u\)表示從起點走到\(u\)時,找到的最小的價格是多少.

  • 入口:\(f_u = a_u\)

  • 轉移:對於\(u\)的任何一條出邊對應的點\(v\),\(f_v = min(f_u,f_v)\)

更新答案\(res = max(res,a_u - f_u)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)

const int N = 2e5+7,M = N;
int edge[M],succ[M],ver[N],idx;
int w[N],deg[N];
ll f[N];
void add(int u,int v)
{
	edge[idx] = v;
	succ[idx] = ver[u];
	ver[u] = idx++;
}
		
int main()
{
	memset(ver,-1,sizeof ver);
	int n,m;scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;++i)	scanf("%d",&w[i]);
	for(int i = 1;i <= m;++i)
	{
		int u,v;scanf("%d%d",&u,&v);
		add(u,v);++deg[v];
	}
	queue<int> q;forn(i,1,n) if(!deg[i])	q.push(i);
	forn(i,1,n)	f[i] = 1e18;
	ll res = -1e18;
	while(!q.empty())
	{
		int u = q.front();q.pop();
		res = max(res,w[u] - f[u]);
		f[u] = min(f[u],1ll*w[u]);
		for(int i = ver[u];~i;i = succ[i])
		{
			int v = edge[i];
			f[v] = min(f[v],f[u]);
			if(--deg[v] == 0)	q.push(v);
		}
	}
	printf("%lld",res);
    return 0;
}

F - +1-1x2

題目大意

給你一個數\(x\),每次可以加一減一或乘二,問使\(x\)變成\(y\)的最小步數.

資料範圍:

\(1 \leq x,y \leq 10^{18}\)

思路

弱智題,顯然.

不過注意分情況討論奇數的時候,既有減一的拉回來的也有加一補上去的情況.

複雜度不知道,直覺是\(log\)的,可以衝的原因是直接的距離可以計算,其次乘2除2的速度非常快.

程式碼

ll x,y;
map<ll,ll> f;

ll solve(ll y)
{
	if(f.count(y))	return f[y];
	ll res = y - x;
	if(y <= x)	return x - y;
	
	if(y % 2 == 0)	res = min(res,solve(y / 2) + 1);
	else			res = min({res,solve(y / 2) + 2,solve((y / 2 + 1)) + 2});
	
	return f[y] = res;
}
int main()
{
	cin >> x >> y;
	cout << solve(y);
    return 0;
}