A Too Rich
题目大意
现在有面值为1,5,10,20,50,100,200,500,1000,2000十种硬币,给定每种硬币的数量。你要选出尽量多的硬币,使得其面值恰好为 p ,无解输出-1。
数据范围
题解
这题假如没有50,500两种硬币的话,相当于剩下
1,5,10,20,100,200,1000,2000
,那么权值小的硬币的面值必为权值大的硬币的面值的约数。那么我们就可以直接贪心地确定每种硬币的数量。具体而言就是从大到小枚举,设当前枚举到第
i
种硬币,
但原题中还有
50,500
两种,但可以注意到的是,假如这两种硬币取了偶数次,必然就可以被面值小的表示出来了。因此,我们可以直接打个爆搜,每种硬币至少取到
c
的下界,并且允许多取一个。多取两个显然是没有意义的。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Val[10] = {1,5,10,20,50,100,200,500,1000,2000};
int C[10],S[10],ans;
void Dfs(int Now,int Least,int Use)
{
if (Least < 0) return;
if (Least == 0)
{
ans = max(ans,Use);
return;
}
int mx = Least - S[Now];
if (mx < 0) mx = 0;
int ch = mx / Val[Now];
if (mx % Val[Now]) ch ++;
if (ch <= C[Now])
Dfs(Now - 1,Least - ch * Val[Now],Use + ch);
++ ch;
if (ch <= C[Now])
Dfs(Now - 1,Least - ch * Val[Now],Use + ch);
}
int main()
{
int T;
scanf("%d", &T);
for(;T;T --)
{
int p;
scanf("%d", &p);
for(int i = 0;i < 10;i ++)
scanf("%d", &C[i]);
for(int i = 1;i <= 10;i ++)
S[i] = S[i - 1] + C[i - 1] * Val[i - 1];
ans = -1;
Dfs(9,p,0);
printf("%d\n", ans);
}
return 0;
}
B Count a * b
题目大意
设
多组询问,给定 n ,求
数据范围
n≤109 , 20000 组询问
题解
首先,我们可以比较轻易的知道:
f(n)=n2−∑ni=1gcd(i,n)
,具体的原理就是总的剪掉不合法的,对于每个
i
,不合法的
记
t(n)=∑ni=1gcd(i,n)
,那么有
t(n)=∑d|nd⋅ϕ(nd)
,
t=id∗ϕ
那么我们可以得到
很显然 g(n) 不是积性函数,而 ∑d|nd2 与 ∑d|nn 都是积性的,因此我们可以分开计算。
但我们始终需要分解质因数,一种是直接用pollard rho来分解,本题中预处理出 <n−−√ 的质数表暴力分解就好了。
比较好的时间复杂度为 O(Tn14) ,我的程序是 O(Tn12logn)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 32000;
typedef unsigned long long ULL;
int Pri[MAXN],cnt;
ULL sqr(ULL a) {return a * a;}
ULL Calc(ULL c,int t)
{
ULL ans = 0;
for(ULL v = 1;t >= 0;t --,v = v * c * c)
ans += v;
return ans;
}
int main()
{
for(int i = 2,n = MAXN - 5;i <= n;i ++)
{
bool f = 1;
for(int j = 2;j * j <= i;j ++)
if (i % j == 0) {f = 0;break;}
if (f) Pri[++ cnt] = i;
}
int T;
scanf("%d", &T);
for(;T;T --)
{
int n;
scanf("%d", &n);
ULL ans1 = 1,ans2 = 1;
for(int i = 1;Pri[i] * Pri[i] <= n;i ++)
{
ULL c = 1,ti = 0;
for(;n % Pri[i] == 0;n /= Pri[i]) c *= Pri[i], ++ ti;
ans1 *= c * (ti + 1);
ans2 *= Calc(Pri[i], ti);
}
if (n > 1)
{
ans1 *= n * 2;
ans2 *= Calc(n,1);
}
printf("%llu\n", ans2 - ans1);
}
return 0;
}
C Play a game
题目大意
现在有一个双方轮流操作的游戏。具体而言就是一开始有一个串
S
和字符串集合
1. 删掉
S
的开头
2. 删掉
若当前操作者操作后使
S
变为空串或
现在给定一个字符串
T
与字符串集合
数据范围
|T|≤40000,∑s∈A|s|≤10000,Q≤40000 ,字符集为小写字母。
题解
一个最简单的dp就是设
f[l][r]
表示
S=T[l..r]
时先手获胜情况。转移十分简单,
f[l][r]=¬f[l+1][r]∨¬f[l][r−1]
。然后当
T[l..r]
为空串或出现在
A
中则将
但注意到
f[l][r]
的值是
0
或
转移同理,
f[l][i]=¬f[l−1][i]∨¬f[l−1][i+1]
,那么用bitset就是
f[l]=¬f[l−1]∨(f[l−1]>>1)
。
唯一剩下的问题就是怎么赋初值,暴力做还是
O(|T|2)
的,但由于
∑s∈A|s|≤10000
,因此,我们可以分
|s|≤100
与
|s|>100
两种情况考虑。对于
l≤100
,暴力枚举每个位置即可。对于
|s|>100
,只有不超过
100
个串,还是对于每个串暴力枚举每个位置即可。
总的时间复杂度就是
O(|T|232+|T|∗100+Q)
(这里复杂度里写了常数非常不标准。。)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <vector>
using namespace std;
const int MAXN = 40001;
const int G[2] = {37,53};
typedef unsigned long long ULL;
struct Node
{
int To[26],fal;
}T[MAXN];
vector<int> Lk[MAXN];
bitset<MAXN> F[MAXN];
char S[MAXN];
bool Fal[MAXN][105];
ULL Pow[2][MAXN],Has[2][MAXN],Inp[2][MAXN];
int N,M,Q,cnt,tot,Len[MAXN];
void Work()
{
scanf("%d%d%d", &N, &M, &Q);
scanf("%s", S + 1);
memset(T,0,sizeof T);
cnt = 1;
for(int i = 0;i <= 40000;i ++) Lk[i].clear();
for(int i = 0;i < 2;i ++)
for(int j = 1;j <= N;j ++)
Has[i][j] = Has[i][j - 1] * G[i] + S[j] - 'a' + 1;
tot = 0;
for(int i = 1;i <= M;i ++)
{
static char Tr[MAXN];
scanf("%s", Tr + 1);
int l = strlen(Tr + 1);
if (l <= 100)
{
int cur = 1;
for(int j = 1;j <= l;j ++)
{
int x = Tr[j] - 'a';
if (!T[cur].To[x]) T[cur].To[x] = ++ cnt;
cur = T[cur].To[x];
}
T[cur].fal = 1;
} else
{
++ tot;
Len[tot] = l;
for(int j = 0;j < 2;j ++)
{
Inp[j][tot] = 0;
for(int k = 1;k <= l;k ++)
Inp[j][tot] = Inp[j][tot] * G[j] + Tr[k] - 'a' + 1;
}
Lk[l].push_back(tot);
}
}
for(int i = 1;i <= N;i ++)
{
int cr = 1;
memset(Fal[i],0,sizeof Fal[i]);
for(int l = 1,p = i;l <= 100 && p <= N;l ++,p ++)
{
cr = T[cr].To[S[p] - 'a'];
Fal[i][l] = T[cr].fal;
}
}
for(int l = 1;l <= N;l ++)
{
F[l] = ~F[l - 1];
F[l] = F[l] | ~(F[l - 1] >> 1);
if (l <= 100)
{
for(int i = 1;i <= N;i ++)
if (Fal[i][l]) F[l][i] = 0;
} else
{
for(int i = 0;i < Lk[l].size();i ++)
{
int v = Lk[l][i];
for(int j = 1;j <= N;j ++)
{
int r = j + l - 1;
if (r > N) break;
bool f = 1;
for(int k = 0;k < 2;k ++)
if (Has[k][r] - Has[k][j - 1] * Pow[k][r - j + 1] != Inp[k][v]) {f = 0;break;}
if (f) F[l][j] = 0;
}
}
}
}
for(int i = 1;i <= Q;i ++)
{
int l,r;
scanf("%d%d", &l, &r);
printf("%d\n", int(F[r - l + 1][l]));
}
}
int main()
{
int T;
scanf("%d", &T);
for(int i = 0;i < 2;i ++)
{
Pow[i][0] = 1;
for(int j = 1;j <= 40000;j ++)
Pow[i][j] = Pow[i][j - 1] * G[i];
}
for(;T;T --) Work();
return 0;
}
D Pipes selection
题目大意
给定
n
个数
数据范围
ai≥0
1≤max(n,s)≤30000
题解
令
pi=∑ij=1ai
,那么显然,问题就变成了找到
l,r
使得
pr−pl=j
。可以发现的是,假如对于每个
j
,我们找到其中位二元组的
不妨对序列分块,令每一块的长度为
L
。
我们现在的目标是确定出每个
时间复杂度为
O(Ls+NLslogs)
,令
L=Nlogs−−−−−−√
取得最好的复杂度
O(sNlogs−−−−−−√)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#define Clear(a) (memset(a,0,sizeof a))
using namespace std;
const int MAXN = 65540,Mo = int(1e9) + 7;
const double PI = acos(-1);
struct Comp
{
double a,b;
Comp(void){}
Comp(double x,double y) : a(x),b(y){}
};
Comp operator +(const Comp &a,const Comp &b) {return Comp(a.a + b.a,a.b + b.b);}
Comp operator -(const Comp &a,const Comp &b) {return Comp(a.a - b.a,a.b - b.b);}
Comp operator *(const Comp &a,const Comp &b) {return Comp(a.a * b.a - a.b * b.b,a.a * b.b + a.b * b.a);}
vector<int> Lk[MAXN];
Comp W[MAXN];
int S[MAXN],Kth[MAXN],Ac[MAXN],Cur[MAXN],Tmp[MAXN],Li[MAXN],R[MAXN],N,L,s,LIM;
void Dft(Comp *a,int sig)
{
static Comp tmp[MAXN];
for(int i = 0;i < L;i ++)
{
int t = 0;
for(int j = i,c = 1;c < L;c <<= 1,j >>= 1) t = ((t << 1) | (j & 1));
tmp[t] = a[i];
}
for(int m = 2;m <= L;m <<= 1)
for(int i = 0,half = m / 2;i < half;i ++)
{
Comp w = W[sig > 0 ? (i * (L / m)) : (L - i * (L / m))];
for(int j = i;j < L;j += m)
{
Comp u = tmp[j],v = w * tmp[j + half];
tmp[j] = u + v;
tmp[j + half] = u - v;
}
}
for(int i = 0;i < L;i ++) a[i] = tmp[i];
}
void Multi(int *A,int *B,int *C)
{
static Comp a[MAXN];
for(int i = 0;i < L;i ++) a[i] = Comp(A[i] + B[i],A[i] - B[i]);
Dft(a,1);
for(int i = 0;i < L;i ++) a[i] = a[i] * a[i];
Dft(a,-1);
for(int i = 0;i < L;i ++) C[i] = int((a[i].a / L + 0.5)) / 4;
}
void Locate(int l,int r,int j)
{
for(int p = l;p <= r;p ++)
if (Kth[j] > Ac[S[p - 1] + j]) Kth[j] -= Ac[S[p - 1] + j]; else
{
int l = Kth[j];
Li[j] = p;
R[j] = Lk[S[p - 1] + j][l - 1];
break;
}
}
void Work()
{
Clear(Lk);
scanf("%d", &N);
for(int i = 1;i <= N;i ++)
scanf("%d", &S[i]), S[i] += S[i - 1],Lk[S[i]].push_back(i);
for(L = 1;L <= S[N] * 2;L <<= 1);
for(int i = 0;i <= L;i ++)
W[i] = Comp(cos(2 * PI * i / L),sin(2 * PI * i / L));
int LIM = sqrt(2 * N * log2(L));
Clear(Ac),Clear(Cur);
for(int i = 1;i <= N;i ++) Ac[S[i]] ++,Cur[S[N] - S[i - 1]] ++;
Multi(Ac,Cur,Tmp);
for(int i = 1;i <= S[N];i ++) Kth[i] = (Tmp[i + S[N]] + 1) / 2;
Clear(Cur);
Clear(Li);
Clear(R);
for(int i = 1;i <= N;i += LIM)
{
int r = min(i + LIM - 1,N);
for(int j = 0;j <= S[N];j ++) Cur[j] = 0;
for(int j = i;j <= r;j ++) Cur[S[N] - S[j - 1]] ++;
Multi(Ac,Cur,Tmp);
for(int j = 1;j <= S[N];j ++)
if (!Li[j])
{
if (Tmp[j + S[N]] >= Kth[j] && Tmp[j + S[N]])
Locate(i,r,j); else Kth[j] -= Tmp[j + S[N]];
}
}
int ans1 = 0,ans2 = 0;
for(int i = 1,p = 1;i <= S[N];i ++)
{
p = p * 233LL % Mo;
ans1 = (ans1 + Li[i] * 1ll * p) % Mo;
ans2 = (ans2 + R[i] * 1ll * p) % Mo;
}
printf("%d %d\n", ans1, ans2);
}
int main()
{
int T;
scanf("%d", &T);
for(;T;T --) Work();
return 0;
}
I Chess Puzzle
题目大意
现在给定一个
N∗M
的网格图,有些格子已经染上了颜色,颜色只有黑或白两种。你要把剩下的格子染上颜色。对于一副网格图,假如存在两个格子
(i,j),(x,y)
,满足:
1.
(i,j)
被染成黑色,
(x,y)
被染成白色;
2.
|i−x|=A,|j−y|=B
那么就能获得1的价值,网格图的价值是所有能获得的价值的总和。
现在给定
N,M,A,B
以及网格图的初始状态,需要求出最大可能的价值,以及在此条件下的字典序最小的染色方案。
数据范围
1≤n,m≤100
题解
将格子看成一个点,假如两个格子
(i,j),(x,y)
满足
|i−x|=A,|j−y|=B
,那么就在新图中其对应的点连上一条边。那么显然,这副图是一个二分图。对这幅图进行二分图染色,那么接下来就是一个非常简单的二元关系了,构出图后用总收益减去最小割即可。剩下的问题就是怎么找到字典序最小的方案,这也是比较简单的。因为是割,这相当于要我们将一些既可以与原点连也可以与汇点连的点分配其所属集合。我们可以贪心来考虑,假如当前一个点已经被确定所属集合,我们就不用管了,否则我们将这个点归属较优的一边,顺便更新其他点的归属情况。
总的复杂度是
O(maxflow(n∗m,n∗m))
。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN = 10005;
struct Node
{
int To,Next,Flow;
Node(void){}
Node(int a,int b,int c) : To(a),Next(b),Flow(c){}
}E[MAXN * 30];
vector<int> Lk[MAXN];
char Ch[105];
int Final[MAXN],D[MAXN],Col[MAXN],Cnt[MAXN],Bel[MAXN],N,M,A,B,S,T,tot;
int Id(int x,int y)
{
return (x - 1) * M + y;
}
void Link(int u,int v,int f)
{
E[++ tot] = Node(v,Final[u],f),Final[u] = tot;
E[++ tot] = Node(u,Final[v],0),Final[v] = tot;
}
bool In(int x,int y)
{
return x > 0 && x <= N && y > 0 && y <= M;
}
void Dfs_C(int Now,int c)
{
Col[Now] = c;
for(int i = 0;i < Lk[Now].size();i ++)
if (Col[Lk[Now][i]] == -1) Dfs_C(Lk[Now][i], !c);
}
bool Bfs()
{
static int Q[MAXN];
for(int i = S;i <= T;i ++) D[i] = -1;
int fi = 1,en = 1;
Q[1] = S;
D[S] = 0;
for(;fi <= en;fi ++)
{
int u = Q[fi];
for(int i = Final[u];i;i = E[i].Next)
if (E[i].Flow > 0 && D[E[i].To] < 0)
{
D[E[i].To] = D[u] + 1;
Q[++ en] = E[i].To;
}
}
return D[T] > -1;
}
int Dfs(int Now,int Flow)
{
if (Now == T) return Flow;
int Use = 0;
for(int i = Final[Now];i;i = E[i].Next)
if (E[i].Flow > 0 && D[E[i].To] == D[Now] + 1)
{
int tmp = Dfs(E[i].To,min(E[i].Flow,Flow - Use));
Use += tmp,E[i].Flow -= tmp,E[i ^ 1].Flow += tmp;
if (Use == Flow) return Use;
}
return Use;
}
void Walk(int Now)
{
Bel[Now] = 1;
for(int i = Final[Now];i;i = E[i].Next)
if (!Bel[E[i].To] && E[i].Flow) Walk(E[i].To);
}
void Walk_R(int Now)
{
Bel[Now] = 2;
for(int i = Final[Now];i;i = E[i].Next)
if (!Bel[E[i].To] && E[i ^ 1].Flow) Walk_R(E[i].To);
}
void Work()
{
tot = 1;
memset(Lk,0,sizeof Lk);
memset(Final,0,sizeof Final),memset(Col,255,sizeof Col);
scanf("%d%d%d%d", &N, &M, &A, &B);
int ans = 0;
for(int i = 1;i <= N;i ++)
for(int j = 1;j <= M;j ++)
{
Cnt[Id(i,j)] = 0;
for(int d = -1;d <= 1;d += 2)
for(int d1 = -1;d1 <= 1;d1 += 2)
{
int x = i + d * A,y = j + d1 * B;
if (In(x,y))
{
Lk[Id(i,j)].push_back(Id(x,y)),Cnt[Id(i,j)] ++;
Link(Id(i,j),Id(x,y),2);
ans += 2;
}
}
}
int n = N * M;
for(int i = 1;i <= n;i ++)
if (Col[i] == -1) Dfs_C(i,Col[i] = 0);
S = 0,T = n + 1;
for(int i = 1;i <= N;i ++)
{
scanf("%s", Ch + 1);
for(int j = 1;j <= M;j ++)
{
int p = Id(i,j);
if (Ch[j] == '.') Link(S,p,Cnt[p]),Link(p,T,Cnt[p]); else
{
if (!Col[p])
Link(S,p,Ch[j] == 'B' ? 1 << 30 : Cnt[p]),Link(p,T,Ch[j] == 'W' ? 1 << 30 : Cnt[p]); else
Link(S,p,Ch[j] == 'W' ? 1 << 30 : Cnt[p]),Link(p,T,Ch[j] == 'B' ? 1 << 30 : Cnt[p]);
}
}
}
while (Bfs()) ans -= Dfs(S,1 << 30);
memset(Bel,0,sizeof Bel);
Walk(S),Walk_R(T);
for(int i = 1;i <= n;i ++)
{
if (Bel[i]) continue;
if (!Col[i]) Walk(i); else Walk_R(i);
}
printf("%d\n", ans / 2);
for(int i = 1;i <= N;i ++)
{
for(int j = 1;j <= M;j ++)
{
int p = Id(i,j);
if (!Col[p])
{
printf("%c", Bel[p] == 2 ? 'W' : 'B');
} else
printf("%c", Bel[p] == 2 ? 'B' : 'W');
}
printf("\n");
}
}
int main()
{
int T;
scanf("%d", &T);
for(;T;T --)
Work();
return 0;
}
K Maximum Spanning Forest
题目大意
现在有一副无限大的格点图。一开始每个格点都是孤立的。现在会加入
n
条边,具体形式是:
(a,b)
为左下角,
(x,y)
为右上角的矩形中的所有格点都向四周相邻的格点连上一条权值为
c
的边。
加入每条边后,你都需要求出当前这幅图的最大生成树的权值。答案对
数据范围
n≤100 ,所有输入的数都满足在 [1,2×109] 。
题解
首先将读入的所有坐标离散化。那么现在相当于在一个
n∗n
的网格图上做了。对于每条网格图上的边,记录当前能覆盖他的最大边权,设
n
表示这条边上实际有的点数,那么我们可以先连上
总的复杂度是
O(n3α(n))
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define pb push_back
using namespace std;
const int MAXN = 505 * 505,Mo = int(1e9) + 7;
typedef long long LL;
struct Edge
{
int u,v,c;
Edge(void){}
Edge(int a,int b,int c) : u(a),v(b),c(c){}
}E[MAXN];
struct Info
{
int x,y,x1,y1,c;
}A[MAXN];
int Fa[MAXN],Ref[505][505],R[505][505],U[505][505],Self[505][505],N;
int Get(int a) {return Fa[a] == a ? a : Fa[a] = Get(Fa[a]);}
void Merge(int u,int v) {Fa[Get(u)] = Get(v);}
void Work()
{
vector<int> X,Y;
X.clear(),Y.clear();
scanf("%d", &N);
for(int i = 1;i <= N;i ++)
{
scanf("%d%d%d%d%d", &A[i].x, &A[i].y, &A[i].x1, &A[i].y1, &A[i].c);
X.pb(A[i].x),X.pb(A[i].x1),Y.pb(A[i].y),Y.pb(A[i].y1);
}
sort(X.begin(),X.end());
sort(Y.begin(),Y.end());
X.erase(unique(X.begin(),X.end()),X.end()),Y.erase(unique(Y.begin(),Y.end()),Y.end());
for(int i = 1;i <= N;i ++)
{
A[i].x = lower_bound(X.begin(),X.end(),A[i].x) - X.begin();
A[i].x1 = lower_bound(X.begin(),X.end(),A[i].x1) - X.begin();
A[i].y = lower_bound(Y.begin(),Y.end(),A[i].y) - Y.begin();
A[i].y1 = lower_bound(Y.begin(),Y.end(),A[i].y1) - Y.begin();
}
for(int i = 0;i < X.size();i ++)
for(int j = 0;j < Y.size();j ++)
{
Ref[i][j] = i * Y.size() + j;
R[i][j] = U[i][j] = Self[i][j] = 0;
}
int tot = 0,ntot = 0,n = X.size(),m = Y.size();
static Edge Bak[MAXN];
for(int p = 1;p <= N;p ++)
{
for(int i = A[p].x;i <= A[p].x1;i ++)
for(int j = A[p].y;j <= A[p].y1;j ++)
{
if (j < A[p].y1) R[i][j] = max(R[i][j],A[p].c);
if (i < A[p].x1) U[i][j] = max(U[i][j],A[p].c);
if (i < A[p].x1 && j < A[p].y1) Self[i][j] = max(Self[i][j],A[p].c);
}
int Ans = 0;
for(int i = 0;i < n;i ++)
for(int j = 0;j < m;j ++)
{
if (j + 1 < m) (Ans += R[i][j] * 1ll * (Y[j + 1] - Y[j] - 1) % Mo) %= Mo;
if (i + 1 < n) (Ans += U[i][j] * 1ll * (X[i + 1] - X[i] - 1) % Mo) %= Mo;
if (i + 1 < n && j + 1 < m)
(Ans += Self[i][j] * ((X[i + 1] - X[i] - 1) * 1ll * (Y[j + 1] - Y[j] - 1) % Mo) % Mo) %= Mo;
}
E[0] = Edge(0,0,1 << 30),E[++ tot] = Edge(0,0,-(1 << 30));
for(int i = 0;i < n * m;i ++) Fa[i] = i;
for(int i = 1;i <= tot;i ++)
{
int u,v;
if (E[i - 1].c > A[p].c && A[p].c >= E[i].c)
{
for(int x = A[p].x;x <= A[p].x1;x ++)
for(int y = A[p].y;y <= A[p].y1;y ++)
{
if (y < A[p].y1)
{
u = Ref[x][y],v = Ref[x][y + 1];
if (Get(u) == Get(v)) continue;
Merge(u,v);
(Ans += A[p].c) %= Mo;
Bak[++ ntot] = Edge(u,v,A[p].c);
}
if (x < A[p].x1)
{
u = Ref[x][y],v = Ref[x + 1][y];
if (Get(u) == Get(v)) continue;
Merge(u,v);
(Ans += A[p].c) %= Mo;
Bak[++ ntot] = Edge(u,v,A[p].c);
}
}
}
if (i == tot) continue;
u = E[i].u,v = E[i].v;
if (Get(u) == Get(v)) continue;
Merge(u,v);
(Ans += E[i].c) %= Mo;
Bak[++ ntot] = E[i];
}
printf("%d\n", Ans);
for(int i = 1;i <= ntot;i ++) E[i] = Bak[i];
tot = ntot;
ntot = 0;
}
}
int main()
{
int T;
scanf("%d", &T);
for(;T;T --) Work();
return 0;
}