1. 程式人生 > 其它 >2021.7.22集訓5賽後總結

2021.7.22集訓5賽後總結

Link

A. 【NOIP2015模擬賽】集合

從最終狀態向前列舉,將不為0的數減1,每次記錄下0的個數 \(sum\),除以2向上取整,最後答案為 \(m + log2(sum)\),其中 \(m\)\(a_i\) 的最大值。

求0的個數可以用桶記錄每個數的個數。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 1000005;
int n,m,a[N],ans,sum;
int t[N];

bool cmp(int x,int y)
{
	return x > y;
}

int qpow(int a,int b)
{
	int res = 1;
	while(b)
	{
		if((b & 1)) res = res * a;
		a *= a;
		b >>= 1;
	}
	return res;
}

int main()
{
	scanf("%d",&n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%d",&a[i]);
		t[a[i]] ++;
		m = max(m,a[i]);
	}
	sort(a + 1, a + 1 + n, cmp);
	sum = 0;
	ans = m + 1;
	for(int i = 1; i <= m + 1; i++)
	{
		sum += t[i - 1];
		if(sum & 1) sum = sum / 2 + 1;
		else sum /= 2;
	}
	ans += log2(sum);
	if(qpow(2,log2(sum)) < sum) ans++;
	printf("%d\n",ans);
	return 0;
}

B. time

因為最多有1000個城市,每個城市最多賺1000哞尼,所以可以發現最多出差1000天,用 \(f_{i,j}\) 表示在第 \(i\) 天走到 \(j\) 最多賺多少錢,答案為 \(max\{f_{i,1}-c*i*i\}(1 \le i \le 1000)\)

\(j -> v\) 轉移方程為 \(f_{i,v} = max(f_{i,v}, f_{i-1,j} + a_v)\)

#include <iostream>
#include <cstdio>
#include <vector>
#include <cmath>
#include <cstring>

using namespace std;

const int N = 1010;
int n,m,c,a[N];
vector <int> G[N];
int f[N][N<<1],ans;

int main()
{
	scanf("%d%d%d",&n,&m,&c);
	for(int i = 1; i <= n; i++)
		scanf("%d",&a[i]);
	for(int i = 1; i <= m; i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		G[u].push_back(v);
	}
	memset(f,128,sizeof(f));
	f[0][1] = 0;
	for(int i = 1; i <= 1000; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			for(int k = 0; k < G[j].size(); k++)
			{
				int v = G[j][k];
				f[i][v] = max(f[i][v],f[i - 1][j] + a[v]);
			}
		}
		ans = max(ans,f[i][1] - c * i * i);
	}
	printf("%d\n",ans);
	return 0;
}

C. threesum

我們要找到所有 \(a_i+a_k+a_j=0\) 的個數,不妨設 \(i < k < j\)

\(sum_{l,r}\) 表示 \(i\)\([1,l]\) 中,\(j\)\([1,r]\) 中的方案數,則答案為矩陣和,即為 \(sum_{r,r} - sum_{l-1,r} - sum_{l,r-1} + sum_{l-1,l-1}\)

\(sum\) 可以用桶記錄下每兩個數的和的個數,但是因為有負數,所以要向右偏移 \(10^6\),之前的和為 \(0\) 就變為 \(3*10^6\)

\(O(n^2)\) 預處理,\(O(1)\) 查詢。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#define ll long long

using namespace std;

const int N = 5010;
const int M = 1e5 + 10;
const int MAXN = 3e6;

int n,m,a[N];
struct query
{
	int l,r,id;
}q[M];
int cnt[MAXN + 10];
ll sum[N][N];

int main()
{
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= n; i++)
		scanf("%d",&a[i]), a[i] += 1e6;
	for(int i = 1; i <= n; i++)
	{
		for(int j = i + 1; j <= n; j++)
		{
			int t = a[i] + a[j];
			if(j > i + 1) if(t <= 3e6 && t >= 1e6) sum[i][j] = cnt[MAXN - t];
			cnt[a[j]] ++;
		}
		for(int j = i + 1; j <= n; j++)
			cnt[a[j]] --;
	}
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
			sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
	for(int i = 1; i <= m; i++)
	{
		scanf("%d%d",&q[i].l,&q[i].r);
		printf("%lld\n",sum[q[i].r][q[i].r] - sum[q[i].l - 1][q[i].r]);
	}
	return 0;
}

D. boards

首先看到 \(1e9\) 的資料範圍,而 \(P\) 只有 \(1e5\) 就知道需要離散化,但是原來的座標也需要存下來。

\(dp_i\) 表示從 \((0,0)\)\((x_i,y_i)\) 的答案。

考慮用 \(j\) 節點更新 \(i\) 節點。

如果可以從 \(j\) 跳到 \(i\)\(dp_i = dp_j\)

否則 \(dp_i=dp_j+x_i-x_j+y_i-y_j\),那麼 \(dp_i-x_i-y_i=dp_j-x_j-y_j\)

\(f_i=dp_i-x_i+y_i\),那麼 \(f_i=f_j\)

如果可以從 \(j\) 跳到 \(i\),那麼\(f_i=f_j+x_j+y_j-x_i-y_i\)

只有 \(x_j \le x_i\)\(y_j \le y_i\) 的點可以更新 \(i\),按 \(x\) 座標順序求 \(f_i\) 然後維護一個字首最小值即可,這裡用的樹狀陣列。

最後答案為 \(min \{f_i+n+n\}\)

因為要離散化,所以跳板的起點和終點都要放進陣列,然後再拍排序。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 2e5 + 10;
int n,m;
struct node
{
	int x,y,Y,id;
}p[N];
int u[N],v[N],f[N];

bool cmpy(node a,node b)
{
	return a.y < b.y;
}

bool cmp(node a,node b)
{
	return a.x == b.x ? a.y < b.y : a.x < b.x;
}

int c[N];

void add(int x,int y)
{
	for(; x <= m * 2; x += x&-x)
		c[x] = min(c[x],y);
}

int qry(int x)
{
	int res = INF;
	for(; x; x -= x&-x)
		res = min(res,c[x]);
	return res;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= m; i++)
	{
		scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i+m].x,&p[i+m].y);
		p[i].id = p[i+m].id = i;
	}
	sort(p + 1, p + 1 + m * 2, cmpy);
	p[0].y = -1;
	for(int i = 1; i <= m * 2; i++)
		p[i].Y = p[i-1].Y + (p[i-1].y != p[i].y);
	sort(p + 1, p + 1 + m * 2, cmp);
	for(int i = 1; i <= m * 2; i++)
	{
		if(u[p[i].id]) v[p[i].id] = i;
		else u[p[i].id] = i;
	}
	// memset(f,0x3f,sizeof(f));
	int ans = INF;
	for(int i = 1; i <= m * 2; i++)
	{
		f[i] = min(f[i],qry(p[i].Y));
		if(u[p[i].id] == i) f[v[p[i].id]] = min(f[v[p[i].id]], f[i] + p[i].x + p[i].y - p[v[p[i].id]].x - p[v[p[i].id]].y);
		add(p[i].Y,f[i]);
		ans = min(ans,f[i]);
	}
	printf("%d\n",ans + n + n);
	return 0;
}