四道 CF。
虽然我没打过 CF,但我每天都在打 CF。
A. [CF1850G] The Morning Star
首先,对于两个互相满足条件的点,其方案数为 \(2\)。
那么对于 \(n\) 个互相满足条件的点,他们对答案的贡献是
\[2 \dbinom{n}{2}=n(n-1) \]然后就是分类讨论四种相互满足条件的情况。
- 横坐标相同的点相互满足条件。
- 纵坐标相同的点相互满足条件。
- 横坐标与纵坐标之和相同的点相互满足条件(左上到右下)。
- 横坐标与纵坐标之差相同的点相互满足条件(左下到右上)。
我们可以用 map
维护相互满足条件的点的数量,由于我们 CF 的优秀赛制,unordered_map
被 hack 了,对着模数卡真哈人。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 200500;
int n,x,y;
long long ans = 0;
map<long long,long long> a1,a2,a3,a4;
int main() {
ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--) {
cin >> n;
for(int i = 1;i <= n; i++) {
cin >> x >> y;
a1[x] ++;
a2[y] ++;
a3[x - y] ++;
a4[x + y] ++;
}
for(auto i : a1)
ans += i.second * (i.second - 1);
for(auto i : a2)
ans += i.second * (i.second - 1);
for(auto i : a3)
ans += i.second * (i.second - 1);
for(auto i : a4)
ans += i.second * (i.second - 1);
cout << ans << "\n";
a1.clear();
a2.clear();
a3.clear();
a4.clear();
ans = 0;
}
return 0;
}
B. [CF1852A] Ntarsis' Set
考虑二分答案。
对于一个数 \(x\),假设删除了 \(y\) 个小于 \(x\) 的数,那么数 \(x\) 的新排名变成 \(x-y\)。
那么我们就枚举 \(k\) 次。对于每一次操作,我们找到最后一个小于等于 \(x\) 的数 \(a_i\) 的下标 \(y\),那么 \(x\) 的新排名就变成了 \(x-y\)。
对于一个数 \(x\),如果它是答案,那么所有小于 \(x\) 的数的排名都会被修改成 \(0\);所有大于 \(x\) 的数的新排名都大于等于 \(1\)。
对于二分的上届,就算 \(a_1,a_2,\dots,a_n\) 是 \(1 \sim n\) 的排列,那么最多也就删掉前面 \(n \times k\) 个数,所以答案一定小于等于 \(n \times k + 1\)。
对于 \(ans\) 的初始值,我们直接设成 \(1\),这样相当于起到了特判的作用。
Code
#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
int N,K;
int a[200500],ans;
bool Check(ll x) {
int k = K;
while(k--) {
int cur = lower_bound(a + 1,a + N + 1,x) - a;
if(a[cur] > x || cur > N)
cur --;
// ↑ 找到 a 里最后一个小于等于 x 的下标 cur
x -= cur;// 删了数后它的新排名
if(x <= 0)
return 0;
}
return 1;
}
signed main() {
ios::sync_with_stdio(false);
int T = 1;
while(T--) {
cin >> N >> K;
for(int i = 1;i <= N; i++)
cin >> a[i];
int l = 1,r = N * K + 1,ans = 1;
while(l <= r) {
int mid = (l + r) >> 1;
if(Check(mid)) {
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
cout << ans << "\n";
for(int i = 1;i <= N; i++)
a[i] = 0;
}
return 0;
}
C. [CF932E] Team Work
\[\begin{align} F(n,k) &=\sum\limits_{i=1}^n{n\choose i}i^k\tag{1}\\ &=n\sum\limits_{i=1}^n{n-1\choose i-1}i^{k-1} \tag{2}\\ &=n\left(\sum\limits_{i=1}^n{n\choose i}i^{k-1}-\sum\limits_{i=1}^n{n-1\choose i}i^{k-1}\right) \tag{3}\\ &=n(F(n,k-1)-F(n-1,k-1)) \tag{4} \end{align} \]对于 \((1) \Rightarrow (2)\),考虑直接提出一个 \(\dfrac{n}{i}\)。
对于 \((2) \Rightarrow (3)\),考虑帕斯卡公式,移项
\[\dbinom{n}{i}=\dbinom{n-1}{i}+\dbinom{n-1}{i-1} \]最后得到一个递归式。
我们发现在计算 \(F(n,k)\) 的过程中,有许多部分是被重复计算的,很明显会 TLE。
由于在递归式中,\(n\) 的范围是 \(n-k \sim n\),所以我们可以记忆化,把时间复杂度降低到 \(\mathcal{O}(k^2)\)。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
#define int long long
const int Mod = 1e9 + 7;
int N,K;
long long fac[2005000];
long long Pow(long long a ,long long b) {
b = (b + Mod - 1) % (Mod - 1);
long long res = 1 ;
long long base = a;
while(b > 0) {
if(b & 1)
res = (res * base) % Mod;
base = (base * base) % Mod;
b >>= 1;
}
return res;
}
long long Inv(long long x) {
return Pow(x % Mod,Mod - 2) % Mod;
}
long long C(long long n,long long m) {
if(m > n)
return 0;
return fac[n] * Inv((fac[m] * fac[n - m])) % Mod;
}
long long Lucas(long long n,long long m) {
if(m == 0)
return 1;
return (Lucas(n / Mod,m / Mod) * C(n % Mod,m % Mod)) % Mod;
}
void Work0() {
fac[0] = 1;
for(int i = 1;i <= N; i++)
fac[i] = fac[i - 1] * i % Mod;
long long ans = 0,tmp;
for(int i = 1;i <= N; i++) {
tmp = Lucas(N,i) * Pow(i,K) % Mod;
ans = (ans + tmp) % Mod;
}
cout << ans;
return ;
}
int vis[5100][5100];
int F(int n,int k) {
if(vis[k][n - (N - K - 2)] != -1)
return vis[k][n - (N - K - 2)];
if(k == 0)
return Pow(2,n) - 1;
return vis[k][n - (N - K - 2)] = (n * (F(n,k - 1) - F(n - 1,k - 1)) % Mod + Mod) % Mod;
}
signed main() {
cin >> N >> K;
if(N <= 200500) {
Work0();
return 0;
}
memset(vis,255,sizeof(vis));
cout << (F(N,K) + Mod) % Mod;
return 0;
}
D. [CF1188D] Make Equal
刚开始读错题意了,还以为是直接求 popcount,显然 T4 不可能这么简单。
然后打了一个假的暴力,只有 \(\text{20 \ pts}\)。
谁能教我 40 分暴力怎么打???
啊哦,原来是枚举太多导致 TLE 了,好,又多挂 20 分!
题目大意
给出 \(n\) 个数字 \(a_1,a_2,\dots,a_n\),每次操作可以给其中一个数加上 \(2\) 的非负整数次幂。求最少的操作次数,使得这 \(n\) 个数相等。
思路
记 \(b_i = \max\limits_{1 \leq k \leq n}{a_k} - a_i\),这道题的目的是求一个值 \(x\) 使得
\[\sum_{i=1}^n \operatorname{popcount}(x + b_i) \]最小,其中 \(\operatorname{popcount}(x)\) 表示数 \(x\) 在二进制下 \(1\) 的个数。
考虑 DP。
考虑二进制下 \(x + b_i\) 的第 \(k\) 位,发现这一位的决策与下面这些有关:
- \(x\) 的第 \(k\) 位是否填 \(1\);
- \(b_i\) 的第 \(k\) 位是否为 \(1\);
- 第 \(k-1\) 位是否进位。
其中,\(x\) 是我们要决策的,\(b_i\) 是已知的,考虑第三种情况。
如果直接进行记录的话很明显要爆炸。但我们发现,第 \(k-1\) 位的进位情况与 \(b_i \mod 2^k\) 有关。
\(b_i \mod 2^k\) 的值越大,就更加容易进位,如果将 \(b_i\) 按照 \(b_i \mod 2^k\) 的值从大到小排序,能产生进位的数一定是 \(b_i\) 的一段前缀。
设 \(dp_{i,j}\) 表示有 \(j\) 个数进位到第 \(i\) 位的最优解(最小操作次数)。
考虑转移,对于第 \(i\) 位,我们要考虑 \(b_k\) 在二进制下第 \(i\) 位是否为 \(1\) 和在第 \(i-1\) 位进位的数的个数。
钦定第 \(i - 1\) 位有 \(j\) 个数进位,记 \(tot\) 为当前这一位为 \(1\) 的 \(b_k\) 的个数;\(cnt\) 为前 \(j\) 个数中当前这一位为 \(1\) 的数的个数。
考虑下面四种情况,表示满足前面两句条件的数有多少个:
- \(x+b_k\) 在第 \(i-1\) 位没有进位,\(b_k\) 的第 \(i\) 位为 \(1\),有 \(tot-cnt\) 个;
- \(x+b_k\) 在第 \(i-1\) 位没有进位,\(b_k\) 的第 \(i\) 位为 \(0\),有 \(n-j-(tot-cnt)\) 个;
- \(x+b_k\) 在第 \(i-1\) 位有进位,\(b_k\) 的第 \(i\) 位为 \(1\),有 \(cnt\) 个;
- \(x+b_k\) 在第 \(i-1\) 位有进位,\(b_k\) 的第 \(i\) 位为 \(0\),有 \(j-cnt\) 个。
那么考虑第 \(i\) 位的决策:
- 如果 \(x\) 取 \(0\),那么第 \(3\) 种产生进位,第 \(1,4\) 种的 \(x+b_k\) 的第 \(i\) 位为 \(1\),产生贡献;
- 如果 \(x\) 取 \(1\),那么第 \(1,3,4\) 种产生进位,第 \(2,3\) 种的 \(x+b_k\) 的第 \(i\) 位为 \(1\),产生贡献;
然后就可以进行转移。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
const int N = 200500;
#define int long long
int n,k;
int a[N],b[N],id[N];
int dp[63][N],num[3][N];
bool cmp(int x,int y);
void Ready();
void Clac(int i,int x,int y);
signed main() {
cin >> n;
for(int i = 1;i <= n; i++)
cin >> a[i];
Ready();
for(k = 0;k <= 60; k++) {// 二进制下每一位
sort(id + 1,id + n + 1,cmp);
for(int i = 1;i <= n; i++) {
num[0][i] = num[0][i - 1];
num[1][i] = num[1][i - 1];
num[b[id[i]] >> k & 1][i] ++;
// 第 k 位为 1 或 0 的数的个数
}
for(int i = 0,x,y;i <= n; i++) {
x = num[0][n] + num[1][n - i] - num[0][n - i];
y = num[1][n] - num[1][n - i];
Clac(i,x,y);
x = num[1][n] + num[0][n - i] - num[1][n - i];
y = n - num[0][n - i];
Clac(i,x,y);
}
}
cout << dp[61][0];
return 0;
}
bool cmp(int x,int y) {
if((b[x] & ((1ll << k) - 1)) < (b[y] & ((1ll << k) - 1)))
return 1;
return 0;
}
void Ready() {
sort(a + 1,a + n + 1);
for(int i = 1;i <= n; i++) {
b[i] = a[n] - a[i];
id[i] = i;
}
memset(dp,0x3f,sizeof(dp));
dp[0][0] = 0;
return ;
}
void Clac(int i,int x,int y) {
dp[k + 1][y] = min(dp[k + 1][y],dp[k][i] + x);
return ;
}
标签:15,int,long,进位,ans,include,CSP,模拟,Mod
From: https://www.cnblogs.com/baijian0212/p/csp-15.html