2021.7.22集訓5賽後總結
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;
}