A n-th
题意
已知公式 π = ∑ k = 0 ∞ 1 1 6 k ( 4 8 k + 1 − 2 8 k + 4 − 1 8 k + 5 − 1 8 k + 6 ) \pi = \sum_{k=0}^{\infty} \frac{1}{16^k} (\frac{4}{8k+1} - \frac{2}{8k+4} - \frac{1}{8k+5} - \frac{1}{8k+6}) π=∑k=0∞16k1(8k+14−8k+42−8k+51−8k+61)
请你求出 π \pi π的十六进制的小数点后的第 n n n位。
思路
如何将一个十进制小数转换为十六进制小数?
以3.1415926
为例 , 转换为小数为3.243f69a25b094
我们需要做的是取小数部分0.1415926
,乘以16后,此时会得到一个数
n
u
m
num
num(
0
≤
n
u
m
<
16
0\le num < 16
0≤num<16 ), 0.1415926 * 16 = 2.2654816
, 那么2
就是
3.1415926
3.1415926
3.1415926 的十六进制小数部分的第一位
之后我们再取小数部分乘以16,0.2654816* 16 = 4.2477056
, 于是就求出了十六进制的小数点后第二位为4
以此类推,我们就可以将一个十进制小数转换为十六进制小数。
因此我们要计算十六进制下 π \pi π 的第n位,也就是计算 1 6 n π 16^n \pi 16nπ 的整数部分。
1 6 d π = 4 ∗ ( 1 6 d ∗ ∑ k = 0 ∞ 1 8 k + 1 ) − 2 ∗ ( 1 6 d ∗ ∑ k = 0 ∞ 1 8 k + 4 ) − ( 1 6 d ∗ ∑ k = 0 ∞ 1 8 k + 5 ) − ( 1 6 d ∗ ∑ k = 0 ∞ 1 8 k + 6 ) 16^d \pi = 4 * (16^d * \sum_{k=0}^\infty \frac{1}{8k+1})-2 * (16^d * \sum_{k=0}^\infty \frac{1}{8k+4})- (16^d * \sum_{k=0}^\infty \frac{1}{8k+5})- (16^d * \sum_{k=0}^\infty \frac{1}{8k+6}) 16dπ=4∗(16d∗∑k=0∞8k+11)−2∗(16d∗∑k=0∞8k+41)−(16d∗∑k=0∞8k+51)−(16d∗∑k=0∞8k+61)
其中 1 6 d ∑ k = 0 ∞ 1 1 6 k ( 8 k + 1 ) = ∑ k = 0 d 1 6 d − k 8 k + 1 + ∑ k = d + 1 ∞ 1 6 d − k 8 k + 1 = ∑ k = 0 d 1 6 d − k 8 k + 1 + ∑ k = d + 1 ∞ 1 6 d − k 8 k + 1 16^d \sum_{k=0}^{\infty} \frac{1}{16^k(8k+1)} \\= \sum_{k=0}^d \frac{16^{d-k}}{8k+1} + \sum_{k={d+1}}^{\infty}\frac{16^{d-k}}{8k+1} \\ = \sum_{k=0}^d \frac{16^{d-k} }{8k+1} + \sum_{k=d+1}^\infty\frac{16^{d-k}}{8k+1} 16d∑k=0∞16k(8k+1)1=∑k=0d8k+116d−k+∑k=d+1∞8k+116d−k=∑k=0d8k+116d−k+∑k=d+1∞8k+116d−k
因为我们只要最终的小数位
对于第二项 ∑ k = d + 1 ∞ 1 6 d − k 8 k + 1 \sum_{k=d+1}^\infty\frac{16^{d-k}}{8k+1} ∑k=d+1∞8k+116d−k,因为 d < k d<k d<k ,所以这一项一定是小于1的
对于第一项,他的小数部分为每项的小数相加,即 ∑ k = 0 d 1 6 d − k m o d ( 8 k + 1 ) 8 k + 1 \sum_{k=0}^d \frac{16^{d-k}\ mod \ (8k+1) }{8k+1} ∑k=0d8k+116d−k mod (8k+1)
因此对于第一部分 ( 1 6 d ∗ ∑ k = 0 ∞ 1 8 k + 1 ) (16^d * \sum_{k=0}^\infty \frac{1}{8k+1}) (16d∗∑k=0∞8k+11),我们就得到了 ∑ k = 0 d 1 6 d − k m o d ( 8 k + 1 ) 8 k + 1 + ∑ k = d + 1 ∞ 1 6 d − k 8 k + 1 \sum_{k=0}^d \frac{16^{d-k}\ mod \ (8k+1) }{8k+1} + \sum_{k=d+1}^\infty\frac{16^{d-k}}{8k+1} ∑k=0d8k+116d−k mod (8k+1)+∑k=d+1∞8k+116d−k 。
(其余三部分按照同样方法处理即可。)
将每部分都计算之后,就会得到一个小于1的数,对这个数再乘上16,那么整数部分就是我们要求的pi的第n位了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
int QuickPow(int x,int n,int mod = INF){
int ans = 1;
while (n != 0){
if ((n & 1) != 0) ans = ans * x % mod;
x = x * x % mod;
n /= 2;
}
return ans;
}
double Calculate16dSj(int d, int j){
double part1 = 0;
double part2 = 0;
// 精度按 d ~ d + 10
int accuracy = d + 10;
for (int k = 0; k <= d; k++)
{
part1 += QuickPow(16, d - k, 8 * k + j) * 1.0 / (8 * k + j);
}
for (int k = d + 1; k < accuracy; k++)
{
part2 += QuickPow(16, d - k) / (8 * k + j);
}
return part1 + part2;
}
double Calculate16dPI(int d)
{
double rlt = 4 * Calculate16dSj(d, 1) - 2 * Calculate16dSj(d, 4) - Calculate16dSj(d, 5) - Calculate16dSj(d, 6);
// 只需要小数部分,所以减去整数部分
rlt -= (int)rlt;
// 因为在计算 16^d * Sj 的第一部分时用到了取模, 所以可能计算结果是负数, 需要加 1
if (rlt < 0) rlt += 1;
return rlt;
}
signed main(){
int n;
cin>>n;
int ans = (int)(Calculate16dPI(n-1) * 16);
char c;
if(ans <= 9) c = '0' + ans;
else c = 'A' + ans - 10;
cout<<c<<endl;
return 0;
}
B a的b次方
题意
给你一个算式,让你在算式左边插入一个幂次符号来分割,并使得等式成立。
如123=1728
, 即
1
2
3
=
1728
12^3=1728
123=1728
约定等式右边的数不大于 1 0 9 10^9 109
思路
本题考查题意模拟。
我们设等号左边的部分为A,右边的部分为B,算式表示为 x n = b x^n = b xn=b。
由于算式太长,我们无法将A和B直接转换为数字,而应该先处理特殊情况:
**情况一:**如果这个算式以0=1
结尾,此时
n
=
0
n = 0
n=0, 那么一定有
x
0
=
1
x^0=1
x0=1 , 我们输出
Y
E
S
YES
YES即可。
情况二:如果A以1
结尾,此时
n
=
1
n = 1
n=1 , 并且A的除最后一个字符外构成的子串与B相等,那么就会出现
x
1
=
x
x^1 = x
x1=x 的情况。此时也应该输出
Y
E
S
YES
YES
**情况三:**如果A[0] == '1'
并且A[1] != '0'
并且B == "1"
,那么就有
1
n
=
1
1^n = 1
1n=1 的情况,输出
Y
E
S
YES
YES。
**情况四:**如果不满足上述三种情况,那么若想使得 x n = b x^n=b xn=b ,就有 x , n ≥ 2 x,n \ge 2 x,n≥2 , 而我们知道 x ≤ b ≤ 1 0 9 x \le \sqrt{b} \le \sqrt{10^9} x≤b≤109 因此x的位数不会超过5位。于是我们可以直接判断当A字符串的长度大于 6 6 6 时, 不存在一种构造方法,输出 N O NO NO
在上述四种情况都判断以后,我们就可以进行字符串转数值的操作了,在转换完之后,我们枚举A的分界点,就可以找出所有情况。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
string s;
cin>>s;
if(s.substr(s.size()-3) == "0=1") {
cout<<"YES"<<endl;return ;
}
int p = 0;
while(p<s.size() && s[p] !='=') p ++;
string A = s.substr(0,p);
string B = s.substr(p+1);
if(A.back() == '1' && A.substr(0,A.size()-1) == B){
cout<<"YES"<<endl;return ;
}
if(A[0] == '1' && A[1]!='0' && B == "1"){
cout<<"YES"<<endl;return ;
}
if(A.size() >= 12) {
cout<<"NO"<<endl;return ;
}
int b = stoll(B);
for(int i = 1;i<A.size();i++){
if(A[i] == '0') continue;
int x = stoll(A.substr(0,i));
int n = stoll(A.substr(i));
if(pow(x,n) == b) {
cout<<"YES"<<endl;return ;
}
}
cout<<"NO"<<endl;
return ;
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
C a^b
题意
给你一个数n( n > 2 ) n > 2) n>2) , 你需要找到一个长度大于1的区间 [ L , R ] [L,R] [L,R] 使得 X O R i = L R i = n XOR_{i=L}^R i = n XORi=LRi=n
思路
异或和公式:
对于任意的正整数 x x x, 有KaTeX parse error: Unknown column alignment: * at position 52: … \begin{array}{*̲*lr**} …
很容易证明该式子是成立的。
我们列举从1开始开始的一些异或和:
x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
X O R i = 1 x XOR_{i=1}^x XORi=1x | 1 | 3 | 0 | 4 | 1 | 7 | 0 | 8 |
可以发现
若
n
m
o
d
4
=
3
n \ mod \ 4 = 3
n mod 4=3 ,那么我们令L = 1,R = n-1
若
n
m
o
d
4
=
0
n \ mod \ 4 = 0
n mod 4=0 那么令L = 1,R = n
即可。
然而对于另外两种情况,我们无法直接由前缀异或得出。
我们发现每次出现两个数,又有两个数无法构造出来。于是我们想到,如果不异或1
,那么就会让最终的答案加1或者减1, 利用这个特性就可以构造出剩下的
n
n
n
x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
X O R i = 2 x XOR_{i=2}^x XORi=2x | 2 | 1 | 5 | 0 | 6 | 1 | 9 |
若
n
m
o
d
4
=
1
n\ mod \ 4 = 1
n mod 4=1 ,那么令L=2,R = n-1
若
n
m
o
d
4
=
2
n\ mod \ 4 = 2
n mod 4=2 ,那么令L=2,R = n
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n;cin>>n;
if(n % 4 == 3) {cout<<"1 "<<n-1<<endl;}
if(n % 4 == 0) {cout<<"1 "<<n<<endl;}
if(n % 4 == 1) {cout<<"2 "<<n-1<<endl;}
if(n % 4 == 2) {cout<<"2 "<<n<<endl;}
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
D b个a相乘
题意
给出 n ( 1 ≤ n ≤ 1 0 5 ) n(1\le n \le 10^5) n(1≤n≤105)个数组成的元组A, 令这个 A A A进行 b ( 1 ≤ b ≤ 1 0 18 ) b(1\le b\le 10^{18}) b(1≤b≤1018)次幂乘法,规定本题中,元组的乘法运算 m u l ( A , B ) mul(A,B) mul(A,B) 为A和B中两两相乘,得到的新的元组。
最终得到
n
b
n^b
nb 个数,对这
n
b
n^b
nb 个数进行取余100操作后,统计答案中0~99
各自出现的次数, 最终的次数对
998244353
998244353
998244353取模。
乘法运算示例:
$ (1,4,16)^2 \ =(1,4,16)(1,4,16) \ = (11,14,116,41,44,416,161,164,1616) \ =(1,4,16,4,16,64,16,64,256)$
思路
本题考查记忆化搜索与快速幂的结合应用。
首先我们知道取余运算有如下公式: ( A ∗ B ) % P = ( ( A % P ) ∗ ( B % P ) ) % P (A*B) \% P = ((A \% P) * (B\%P))\%P (A∗B)%P=((A%P)∗(B%P))%P , 在本题中 P = 100 P = 100 P=100 , 因为在计算过程中,我们只需要知道这个数在取余100后的值即可。
于是我们就可以如下设计dp数组: d p [ i ] [ j ] dp[i][j] dp[i][j]表示 A i A^i Ai 时, j ( 0 ≤ j ≤ 99 ) j(0\le j \le 99) j(0≤j≤99)出现的次数。
而 m u l ( A x , A y ) = A x + y mul(A^x,A^y) = A^{x+y} mul(Ax,Ay)=Ax+y (这里的 m u l mul mul为本题定义的乘法运算) 。我们已知 A x A^x Ax 中存在 d p [ x ] [ i ] dp[x][i] dp[x][i] 个值为 i i i 的项, A y A^y Ay 中存在 d p [ y ] [ j ] dp[y][j] dp[y][j] 个值为 i i i 的项,那么二者会为 A x + y A^{x+y} Ax+y中提供 d p [ x ] [ i ] ∗ d p [ y ] [ j ] dp[x][i]*dp[y][j] dp[x][i]∗dp[y][j] 个值为 ( i + j ) % 100 (i+j)\%100 (i+j)%100 的项。
在递推式中体现为 d p [ x + y ] [ ( i + j ) % 100 ] = ( d p [ x + y ] [ ( i + j ) % 100 ] + d p [ x ] [ i ] ∗ d p [ x ] [ j ] ) % 998244353 dp[x+y][(i+j)\% 100] = (dp[x+y][(i+j)\% 100] + dp[x][i]*dp[x][j])\%998244353 dp[x+y][(i+j)%100]=(dp[x+y][(i+j)%100]+dp[x][i]∗dp[x][j])%998244353
但是由于b的取值为 1 ≤ b ≤ 1 0 18 1\le b \le 10^{18} 1≤b≤1018 ,我们无法一项一项地推出 A b A^b Ab的值,联想到快速幂的过程,我们可以得到如下递推式:
KaTeX parse error: Unknown column alignment: * at position 44: … \begin{array}{*̲*lr**} …
根据递推式,我们可以用记忆化搜索来处理这个过程。
由于dp的第一维范围过大(与b的范围相同,为 1 ≤ b ≤ 1 0 18 1\le b\le 10^{18} 1≤b≤1018), 因此我们不能以数组的方式存储 d p [ i ] [ j ] dp[i][j] dp[i][j] ,第一项的大小是不允许的。
于是我们可以将dp数组的第一维离散化处理(或使用map来记录)。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
const int mod = 998244353;
vector<PII> mul[100];
int n,b;
map<int,array<int,100> > dp;
void dfs(int now){
if(dp.find(now) != dp.end()) return ;
array<int,100> dp1,dp2;
if(now & 1){
dp1 = dp[1];
if(dp.find(now-1) == dp.end()) dfs(now-1);
dp2 = dp[now-1];
}else{
if(dp.find(now/2) == dp.end()) dfs(now/2);
dp1 = dp[now/2];
dp2 = dp[now/2];
}
array<int,100> dpnow;
for(int i = 0;i<=99;i++) dpnow[i] = 0;
for(int i = 0;i<=99;i++){
for(auto [l,r] : mul[i]){
dpnow[i] = (dpnow[i] + dp1[l]*dp2[r]%mod)%mod;
}
}
dp[now] = dpnow;
return ;
}
void solve(){
for(int i = 0;i<=99;i++){
for(int j = 0;j<=99;j++){
mul[i*j%100].push_back({i,j});
}
}
cin>>n>>b;
array<int,100> tmp;
for(int i = 0;i<=99;i++) tmp[i] = 0;
for(int i = 1;i<=n;i++){
int x;cin>>x;
tmp[x%100] ++;
}
dp[1] = tmp;
dfs(b);
array<int,100> ans = dp[b];
for(int i = 0;i<=99;i++){
cout<<ans[i]<<' ';
}
}
int qpow(int x,int n){
int ans = 1;
while(n){
if(n&1){
ans = ans * x % mod;
}
x = x * x % mod;
n>>=1;
}
return ans;
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
E 小猪吃蛋糕
题意
一共有n天,你每天可以赚 a i a_i ai 个金币
有两种商品:
- 商品一售价 x x x ,价值 u u u
- 商品二售价 y y y ,价值 v v v
问如何购物才能有最大的价值。
(保证 l c m ( x , y ) ∣ ∑ i = 1 n a i lcm(x,y) | \sum_{i=1}^na_i lcm(x,y)∣∑i=1nai)
注:小猪可以选择将没花完的钱留在之后使用。
思路
—本题不是背包问题!—
因为小猪可以把钱留在之后花,所以完全可以留在最后一天再购物。
并且因为题目保证了 l c m ( x , y ) ∣ ∑ i = 1 n a i lcm(x,y) | \sum_{i=1}^na_i lcm(x,y)∣∑i=1nai ,因此最终无论买哪个商品都是不会有钱没花掉。
于是我们选择性价比最高的商品来买就好了:
设小猪总共的钱为 s u m = ∑ i = 1 n a i sum = \sum_{i=1}^na_i sum=∑i=1nai,最终答案即为 m a x ( s u m x ∗ u , s u m y ∗ v ) max(\frac{sum}{x}*u,\frac{sum}{y}*v) max(xsum∗u,ysum∗v)
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int x,y,u,v;
cin>>x>>u>>y>>v;
int n;
cin>>n;
int sum = 0 ;
for(int i =1;i<=n;i++){
int x;cin>>x;sum += x;
}
cout<<max(sum/x*u,sum/y*v);
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
F 猫猫虫配对
题意
给 2 N ( 1 ≤ N ≤ 8 ) 2N(1\le N \le 8) 2N(1≤N≤8) 个数,以及一个你需要两两配对, 若 i , j i,j i,j 进行配对, 会产生 A i , j A_{i,j} Ai,j 的价值
问这N个价值的异或和,最大是多少。
思路
本题考查dfs搜索。
可以求得总的方案数量为 C 2 N 2 ∗ C 2 N − 2 2 ∗ . . . ∗ C 2 2 N ! = ( 2 N ) ! N ! 2 N \frac{C_{2N}^2 * C_{2N-2}^2*...*C_2^2}{N!} = \frac{(2N)!}{N!2^N} N!C2N2∗C2N−22∗...∗C22=N!2N(2N)! , 当N最大时,方案数为 16 ! 8 ! 2 8 = 2 , 027 , 025 \frac{16!}{8!2^8}=2,027,025 8!2816!=2,027,025 。在程序的容忍度之内。
因此我们使用dfs枚举所有的方案,找其中的最大值即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
#define N 21
int vis[N];
vector<int>v;
int ar[21][21];
int n;
int res=0;
int ans=0;
void dfs(int x,int sum){
if(x==n){
res++;
ans=max(ans,sum);
return;
}
if(vis[x]){
dfs(x+1,sum);
return;
}
for(int i=x+1;i<=n;++i){
if(!vis[i]){
vis[i]=1;
dfs(x+1,sum^ar[x][i]);
vis[i]=0;
}
}
}
void solve() {
cin>>n;
n*=2;
for(int i=1;i<n;++i){
for(int j=i+1;j<=n;++j){
cin>>ar[i][j];
ar[j][i]=ar[i][j];
}
}
dfs(1,0);
cout<<ans<<endl;
}
signed main(){
IO;
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
return 0;
}
G 排队(easy)
题意
有 m m m个柜台可以并行处理任务,有 n n n个任务要按顺序进行,每个任务所需要的时间为 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 。你是第n+1个任务,问什么时候能轮到你的任务,并且你需要去哪个柜台。
(如果有多个柜台同时空闲,此时你需要去标号最小的那个柜台)
easy版本中 1 ≤ m ≤ 10 1\le m \le 10 1≤m≤10
思路
因为柜台数量很少,所以我们可以设置一个长度为 m m m的数组 s e r v e r server server , 其中 s e r [ i ] ser[i] ser[i]表示第 i i i个柜台会在什么时候重新开放。
于是初始时, s e r v e r server server数组全为0。
当第 i i i个任务到来时,我们按顺序枚举所有的柜台,找到值最小的柜台,代表他是最先开放的。然后我们让他的值加上 a [ i ] a[i] a[i] ,表示这个柜台在处理完当前任务后,时间会过去 a [ i ] a[i] a[i]
我们处理所有的1-n
个任务,这样就可以知道轮到我们时的柜台的状态了。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
int server[15];
void solve(){
int n,m;cin>>n>>m;
for(int i = 1;i<=n;i++){
int x;cin>>x;
int minp = 1; // 时间最小的那个柜台的标号
for(int i= 2;i<=m;i++){
if(server[i] < server[minp]){ // 这里不能写成<=,因为有多个最小值时选择小标号
minp = i;
}
}
server[minp] += x;
}
int minp = 1;
for(int i = 2;i<=m;i++){
if(server[i] < server[minp]){
minp = i;
}
}
cout<<server[minp]<<" "<<minp<<endl;
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
H 排队(hard)
题意
有 m m m个柜台可以并行处理任务,有 n n n个任务要按顺序进行,每个任务所需要的时间为 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 。你是第n+1个任务,问什么时候能轮到你的任务,并且你需要去哪个柜台。
(如果有多个柜台同时空闲,此时你需要去标号最小的那个柜台)
hard版本中 1 ≤ m ≤ 1 0 5 1\le m \le 10^5 1≤m≤105
思路
我们观察简单版本的思路,会发现我们每次要找的就是 s e r v e r server server中最小的那个值,但是如果暴力枚举的话,时间复杂度是 O ( n m ) O(nm) O(nm)是不被允许的,于是我们想到了堆(也就是优先队列,是一种能够在 l o g m logm logm的复杂度下进行删除和插入元素的数据结构) 。
因为要依赖时间和柜台标号两个键进行排序,因此推荐创建一个类,并重载比较函数来规定排序规则。
对于 s e r v e r [ x ] = s e r v e r [ x ] + a [ i ] server[x] = server[x] + a[i] server[x]=server[x]+a[i] 操作,我们可以等价换做使原先的元素出队,然后入队一个值为 s e r v e r [ x ] + a [ i ] server[x] + a[i] server[x]+a[i],下标和出队元素下标相同的元素。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
struct node{
int time,id;
bool operator < (const node& a) const{ // 重载排序函数
if(time != a.time) return time > a.time;
return id > a.id;
}
};
void solve(){
priority_queue<node> pq;
int n,m;
cin>>n>>m;
for(int i = 1;i<=m;i++){
pq.push({0,i});
}
for(int i = 1;i<=n;i++){
int x;cin>>x;
node t = pq.top();pq.pop();
pq.push({t.time+x,t.id});
}
cout<<pq.top().time<<' '<<pq.top().id;
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}
I 近距猎兵型
题意
给定 n , m n,m n,m 。 表示为有n个圆心在原点的同心圆,半径为 1 , 2 , 3 , . . . , n − 1 , n 1,2,3,...,n-1,n 1,2,3,...,n−1,n ,以及m个角度相等的过圆心的直线。
较易得知,直线将每个圆划分为 2 m 2m 2m个等长的弧线。形成了 2 m 2m 2m个交点。
问:交点之间两两最短路径的和是多少?(答案向下取整)
(只要两点相同即被认为是相同的点对,即若有交点A B
, 那么点对(A,B)
与(B,A)
应当被认为是同一个点对)
最短路径表示为从一个交点出发,沿着圆或直线到达另一个交点,所走过的路径的最短距离。
( 1 ≤ n , m ≤ 500 1\le n,m \le 500 1≤n,m≤500)
思路
我们先计算单位圆被切割为 2 m 2m 2m 个交点后,这些交点产生的贡献。
假设要计算点 i i i 与点 j j j 的路径,那么有两种路径可以选择:
- 绕着圆的半径到达,路径长度为点 i i i和点 j j j之间的弧度。
- 先沿直线到达圆心,再沿着直线到达另一个点,路径长度为2。
于是我们得知当弧度小于等于2时,选择方案一更近,弧度大于2时选择方案二更近。
根据m的大小,便可以得出当 i i i和 j j j之间相差超过多少时,弧度会大于2。我们设这个值为 l i m lim lim
那么可以得到如下公式: l i m = ⌊ 2 ∗ m π ⌋ lim = \lfloor\frac{2*m}{\pi} \rfloor lim=⌊π2∗m⌋ 。 其中$\lfloor x \rfloor 表示对 表示对 表示对x$向下取整。
对于圆上的一个点,有 2 ∗ l i m 2 * lim 2∗lim 个点与它的角度距离小于 2 2 2 ,该点到这些点之间的距离的和为
d i s 1 ′ = 2 ∗ ( l i m ∗ ( l i m + 1 ) 2 ∗ ( 2 π 2 m ) ∗ 1 ) = l i m ∗ ( l i m + 1 ) ∗ π m dis_1' = 2 * (\frac{lim*(lim+1)}{2} * (\frac{2\pi}{2m})*1) = \frac{lim*(lim+1)*\pi}{m} dis1′=2∗(2lim∗(lim+1)∗(2m2π)∗1)=mlim∗(lim+1)∗π
对于其余的 2 ∗ m − 2 ∗ l i m − 1 2*m-2*lim-1 2∗m−2∗lim−1 个点,距离均为2 ,所以距离和为
d i s 1 ′ ′ = 4 ∗ m − 4 ∗ l i m − 2 dis_1'' = 4*m-4*lim-2 dis1′′=4∗m−4∗lim−2
因此单位圆上的一个点到其他点的距离和即为 d i s 1 = d i s 1 ′ + d i s 1 ′ ′ = l i m ∗ ( l i m + 1 ) ∗ π m + ( 4 ∗ m − 4 ∗ l i m − 2 ) dis_1 = dis_1' + dis_1'' = \frac{lim*(lim+1)*\pi}{m} + (4*m-4*lim-2) dis1=dis1′+dis1′′=mlim∗(lim+1)∗π+(4∗m−4∗lim−2)
由此拓展到半径为 i i i 的圆上,该圆上的一个交点与同一个圆上的其他交点的距离的和为 d i s i = i ∗ d i s 1 dis_i = i * dis_1 disi=i∗dis1 。
于是可得半径为 i i i的圆上两两点产生的距离的和为: s u m i ′ = m ∗ d i s i sum_i' = m * dis_i sumi′=m∗disi
假设有半径为 i i i的圆上的交点 a a a, 那么它到半径为 j j j的圆上的所有的交点距离的和为 d i s j + 2 ∗ m ∗ ∣ i − j ∣ dis_j + 2 * m *|i-j| disj+2∗m∗∣i−j∣ 。
于是对于所有 j < i j < i j<i的点,他们与点 a a a的距离的和为 c o n t i = ∑ j = 1 i − 1 ( d i s j + 2 ∗ m ∗ ( i − j ) ) = ∑ j = 1 i − 1 d i s j + 2 ∗ m ∗ i ∗ ( i − 1 ) − 2 ∗ m ∗ ∑ j = 1 i − 1 j cont_i = \sum_{j=1}^{i-1} (dis_j + 2 * m *(i-j)) = \sum_{j=1}^{i-1} dis_j + 2 * m * i*(i-1) - 2*m*\sum_{j=1}^{i-1} j conti=∑j=1i−1(disj+2∗m∗(i−j))=∑j=1i−1disj+2∗m∗i∗(i−1)−2∗m∗∑j=1i−1j
化简可得 c o n t i = ∑ j = 1 i − 1 d i s j + 2 ∗ m ∗ ( i − 1 ) ∗ i − 2 ∗ m ∗ i ∗ ( i − 1 ) 2 = ∑ j = 1 i − 1 d i s j + m ∗ ( i 2 − i ) cont_i =\sum_{j=1}^{i-1} dis_j + 2 * m * (i-1)*i - 2 * m * \frac{i*(i-1)}{2} = \sum_{j=1}^{i-1} dis_j + m *(i^2-i) conti=∑j=1i−1disj+2∗m∗(i−1)∗i−2∗m∗2i∗(i−1)=∑j=1i−1disj+m∗(i2−i)
其中第一项 ∑ j = 1 i − 1 d i s j \sum_{j=1}^{i-1} dis_j ∑j=1i−1disj可以由 ∑ j = 1 i − 2 d i s j + d i s i − 1 \sum_{j=1}^{i-2}dis_j + dis_{i-1} ∑j=1i−2disj+disi−1递推得来。
那么对于所有的半径为 i i i的圆上的点,到所有半径为 j ( j < i ) j(j<i) j(j<i) 的距离的和为
s u m i ′ ′ = 2 ∗ m ∗ c o n t i sum_i'' = 2 * m * cont_i sumi′′=2∗m∗conti
由上式可得所有圆上的交点两两之间的距离和为 a n s = ∑ i = 1 n ( s u m i ′ + s u m i ′ ′ ) ans = \sum_{i=1}^n (sum_i' + sum_i'') ans=∑i=1n(sumi′+sumi′′)
需要注意的是,如果 m > 1 m>1 m>1 ,那么多条直线会在原点形成一个额外的交点,需要额外计算它与其他点的距离的和:
a n s = a n s + n ∗ m ∗ ( n + 1 ) ans = ans + n * m * (n + 1) ans=ans+n∗m∗(n+1)
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define ld long double
#define pii pair<int,int>
#define complex complex<ld>
#define rand mt19937_64
#define endl '\n'
#define PI (ld)(3.141592653589793)
#define INF (int)(1e8+1)
#define MOD (int)(1e9+7)
#define eps (ld)(1e-9)
#define mpair(x,y) make_pair(x,y)
#define all(x) x.begin(),x.end()
#define lowbit(x) (x&-x)
rand rnd(clock());
int n, m;
void solve() {
cin >> n >> m;
ld lim = floor(2.0 * m / PI);
ld dis1 = lim*(lim+1)*PI/m + (4*m-4*lim-2);
ld ans = 0;
ld z = 0;
ld dis = 0; // 从1到i的dis的和
for (int i = 1;i <= n;++i) {
ld cont = dis + m * (i*i-i);
ld sum1 = m * dis1 * i;
dis += i * dis1;
ld sum2 = 2 * m * cont;
ans += sum1+sum2;
}
if(m>1){
ans += n * m * (n + 1);
}
cout<<((int)ans)<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
// freopen("test.err", "w", stderr);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
J 闪蝶
题意
给你一个长度为 n n n的数组 a a a , ( a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an) 保证 1 ≤ a i ≤ m 1\le a_i \le m 1≤ai≤m
进行q次询问,每次询问给出区间 [ l , r ] [l,r] [l,r] 。
你需要求出数组a的区间 [ l , r ] [l,r] [l,r]内每种数出现的次数 c 1 , c 2 , . . . , c m c_1,c_2,...,c_m c1,c2,...,cm , 输出 c 1 2 + c 2 2 + . . . + c m 2 c_1^2+c_2^2+...+c_m^2 c12+c22+...+cm2
( 1 ≤ n , m , q ≤ 1 0 5 1\le n,m,q \le 10^5 1≤n,m,q≤105)
思路
本题考查莫队算法,如果你没了解过该算法,请移步https://oi-wiki.org/misc/mo-algo/
我们发现本题要求的值是满足莫队的性质的,即可以在 O ( 1 ) O(1) O(1)的时间复杂度内将 [ l , r ] [l,r] [l,r]的结果扩展到 [ l − 1 , r ] , [ l + 1 , r ] , [ l , r − 1 ] , [ l , r + 1 ] [l-1,r],[l+1,r],[l,r-1],[l,r+1] [l−1,r],[l+1,r],[l,r−1],[l,r+1] ,具体为:
我们将区间向外扩展一个数字 a i a_i ai,会让 c a i c_{a_i} cai 的值加一,于是答案增加 ( c a i + 1 ) 2 − c a i 2 = 2 ∗ c a i + 1 (c_{a_i}+1)^2 - c_{a_i}^2 = 2 * c_{a_i} + 1 (cai+1)2−cai2=2∗cai+1 。
对于将区间向内收缩一个数字 a i a_i ai ,同理可得答案会减少 2 ∗ c a i − 1 2 * c_{a_i} - 1 2∗cai−1
于是我们按照莫队算法的求解过程,我们将这q组询问进行排序后,不断扩展收缩,并得出答案即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ld long double
#define pii pair<int,int>
#define all(x) x.begin(),x.end()
#define lowbit(x) (x&-x)
#define endl '\n'
#define INF 1e14
#define MOD 998244353
#define lc p<<1
#define rc p<<1|1
const int N = 2e5 + 5;
int n, m, k;
int a[N];
int seg;
int ans[N];
typedef struct {
int l, r;
int id;
}Query;
Query q[N];
bool cmp(Query a, Query b) {
if (a.l / seg != b.l / seg) return a.l < b.l;
return a.r < b.r;
}
int res, cnt[N];
void add(int v) {
res += 2 * (cnt[v]++) + 1;
}
void del(int v) {
res += -2 * (cnt[v]--) + 1;
}
void solve() {
cin >> n >> k;
seg = pow(n, 0.5);
for (int i = 1;i <= n;++i)
cin >> a[i];
cin >> m;
for (int i = 1;i <= m;++i) {
cin >> q[i].l >> q[i].r;
q[i].id = i;
}
sort(q + 1, q + m + 1, cmp);
for (int i = 1, l = 1, r = 0;i <= m;++i) {
while (l > q[i].l) add(a[--l]);
while (r < q[i].r) add(a[++r]);
while (l < q[i].l) del(a[l++]);
while (r > q[i].r) del(a[r--]);
ans[q[i].id] = res;
}
for (int i = 1;i <= m;++i)
cout << ans[i] << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
//cin >> t;
while (t--) solve();
return 0;
}
K 奶龙摇花手
题意
有n个城市,他们之间两两互通,城市之间的距离由矩阵 D D D 表示,即 D i , j D_{i,j} Di,j表示从 i i i到 j j j的距离的权重。
你需要从城市1到达城市n,并且给定了整数 A , B , C A,B,C A,B,C 。
从城市 i i i到城市 j j j有两种方案:
方案一:花费 D i , j ∗ A D_{i,j}*A Di,j∗A的时间从城市 i i i到达城市 j j j。
方案二:花费 D i , j ∗ B + C D_{i,j}*B+C Di,j∗B+C的时间从城市 i i i到达城市 j j j。
但有如下限制:一旦你开始使用方案二,那么在此之后都只能使用方案二来移动,无法切换到方案一。(相反,可以随时从方案一切换到方案二)
问最少需要多少时间能从城市 1 1 1到达城市 n n n?
思路
注意本题使用floyd算法会导致超时!
本题中的两种方案可以当作图中的两个边来看待,我们可以用dijstra来找到图的起点到终点的最短路。
由于有限制当使用过方案二后,之后只能使用方案二。因此我们在记录节点状态时,除了要记录已经花费的时间 t i m tim tim 和当前的节点编号 i d id id ,还要记录当前是否使用过方案二 o p op op 。
起点的状态为(0,1,0)
, 然后执行dijstra(优先队列BFS),在节点转移时根据op的状态来决定能否使用方案一来转移,以及转移后的状态,当我们第一次到达终点时,此时的时间就是所花费的最少的时间。
注意我们用vis数组来记录某个节点是否到达过时,同样需要二维,分别表示 i d id id和 o p op op的值。
L 抽象数组
题意
有两个长度为 n n n的数组 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 和 b 1 , b 2 , . . . , b n b_1,b_2,...,b_n b1,b2,...,bn 。我们称他们的抽象程度为 ∑ i = 1 n ( ∣ a i − b i ∣ ) \sum_{i=1}^n(|a_i-b_i|) ∑i=1n(∣ai−bi∣) , 其中 ∣ x ∣ |x| ∣x∣ 表示 x x x的绝对值。
你可以进行最多一次如下操作,来使得抽象程度尽可能地大:
- 选择两个索引 i , j ( 1 ≤ i < j ≤ n ) i,j (1\le i < j \le n) i,j(1≤i<j≤n)
- 交换 b i b_i bi 与 b j b_j bj的值。
请问最终的抽象程度最大为多少。
思路
我们可以把 ( a i , b i ) (a_i,b_i) (ai,bi) 视为数轴上的一段线段。这个线段的两端为 a i , b i a_i,b_i ai,bi 。
那么本题中的抽象值,即为所有线段的长度的和。
如果我们想要通过交换 b i , b j b_i,b_j bi,bj 来使得这个值变大,那么就需要保证 两个线段在交换前没有交集。
可以发现交换能够使得贡献增加 2 ∗ ( ∣ a j − b i ∣ ) 2*(|a_j-b_i|) 2∗(∣aj−bi∣) 。通过画图可以发现,对于其他不相交线段的情况,交换后同样会使得贡献增加中间区间的长度的两倍。
于是我们便不需要关心 a i , b i a_i,b_i ai,bi 的大小关系, 只需要关心区间的左右端点即可,令 l [ i ] = m i n ( a i , b i ) , r [ i ] = m a x ( a i , b i ) l[i] = min(a_i,b_i) , r[i] = max(a_i,b_i) l[i]=min(ai,bi),r[i]=max(ai,bi) 。
如果我们交换区间 [ l i , r i ] [l_i,r_i] [li,ri]和 [ l j , r j ] [l_j,r_j] [lj,rj] 那么就会增加 2 ∗ ( l j − r i ) 2*(l_j-r_i) 2∗(lj−ri) 的贡献, 于是我们找到最大的 l i l_i li 以及最小的 r j r_j rj ,便可以得出答案。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n;
cin>>n;
vector<int> a(n+5);
vector<int> b(n+5);
rep(i,1,n) cin>>a[i];
rep(i,1,n) cin>>b[i];
int ans = 0;
rep(i,1,n){
ans = ans + abs(a[i]-b[i]);
}
int mxl = 0;
for(int i = 1;i<=n;i++){
int l = min(a[i],b[i]);
mxl = max(mxl,l);
}
int mnr = INF;
for(int i = 1;i<=n;i++){
int r = max(a[i],b[i]);
mnr = min(mnr,r);
}
if(mxl > mnr) ans += 2 * (mxl - mnr);
cout<<ans<<endl;
}
signed main(){
int T = 1;
// cin>>T;
while(T--){
solve();
}
return 0;
}