2021牛客暑期多校(三)
B: Black and white
题目大意
一个 n ∗ m n*m n∗m的棋盘,将点 ( i , j ) (i, j) (i,j)染黑需要花费 c ( i , j ) c(i, j) c(i,j),但是如果两行两列的四个交点中有三个已经被染黑,那么第四个点可不支付代价直接染黑。求最终将整个棋盘染黑的最小花费。 c ( i , j ) c(i, j) c(i,j)由式子 A 0 = a , A ( i + 1 ) = ( A i ∗ A i ∗ b + A i ∗ c + d ) A0 = a,A(i+1) = (Ai * Ai * b + Ai * c + d)% p A0=a,A(i+1)=(Ai∗Ai∗b+Ai∗c+d)生成。 ( a , b , c , d < p ≤ 100000 , n , m ≤ 5000 ) (a,b,c,d < p ≤ 100000,n,m≤5000) (a,b,c,d<p≤100000,n,m≤5000)。
思路
将染黑点 ( i , j ) (i, j) (i,j)转化为在 A i A_i Ai和 B j B_j Bj之间花费 c ( i , j ) c(i, j) c(i,j)连一条边,那么染黑整个棋盘转化为连出一个完全图。如果两行两列的四个交点中染黑了三个,那么也就是 A i A_i Ai, A j A_j Aj, B i B_i Bi, B j B_j Bj四个点间连了三条边,即这四个点连通了。而根据如果两行两列的四个交点中三个已经被染黑那么第四个点直接染黑,即可推出对于任意四个点,连通即符合条件。那么题目要求就从连出一个完全图的最小花费转化为连通的最小花费,即最小生成树。稠密图正解是 p r i m prim prim,但是 k r u s k a l kruskal kruskal卡常也能过。 k r u s k a l kruskal kruskal对边排序时在结构体内重载运算符要比手写 c m p cmp cmp函数快得多。
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, a, b, c, d, p;
int fa[10001];
long long ans, sum;
struct edge
{
int u, v;
long long qz;
bool operator < (const edge & x) const { return qz < x.qz; }
}ed[25000001];
int find(int x)
{
if(fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
void kruskal()
{
for(int i = 1; i <= n*m; i++)
{
int xx = find(ed[i].u), yy = find(ed[i].v);
if(xx != yy)
{
fa[yy] = xx;
ans += ed[i].qz;
sum++;
if(sum == m+n-1)
return;
}
}
}
int main()
{
scanf("%d %d %d %d %d %d %d", &n, &m, &a, &b, &c, &d, &p);
ed[0].qz = a;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
int ind = m*(i-1)+j;
ed[ind] = {i, j+n, (ed[ind-1].qz*ed[ind-1].qz*b+ed[ind-1].qz*c+d)%p};
}
sort(ed+1, ed+n*m+1);
for(int i = 1; i <= m+n; i++)
fa[i] = i;
kruskal();
printf("%lld", ans);
return 0;
}
C: Minimum grid
题目大意
一个 n ∗ n n*n n∗n的棋盘,其上一些格子可以放数字,一些不能放。现在给出棋盘的大小 n n n,可以放数字的格子数 m m m,最大的 m a x max max限制 k k k,每一行每一列的最大值 m a x max max以及每个可放数字格子的坐标,求填数后满足条件的最小数字和。题目保证有一组解满足条件。 ( 1 ≤ n ≤ 2 × 1 0 3 , 1 ≤ m ≤ 8 × 1 0 5 , 1 ≤ k ≤ 1 0 6 ) (1≤n≤2×10^3,1≤m≤8×10^5,1≤k≤10^6) (1≤n≤2×103,1≤m≤8×105,1≤k≤106)。没有数字在每一行每一列的最大值中出现超过 500 500 500次。
思路
问题要求最小的数字和,那么要让大的数出现的次数尽可能少,也即一个大的数尽可能同时满足行和列的限制。考虑如果没有不能放数的格子,那么对于最大的最大值 x x x来说,如果在行最大中出现了 a a a次,在列最大中出现了 b b b次,那么其对答案的贡献为 x ∗ ( a + b − m i n ( a , b ) ) x*(a + b - min(a, b)) x∗(a+b−min(a,b))。但是现在可能 ( i , j ) (i, j) (i,j)点不能放数字,那么也就是不能将 x x x放在 A i A_i Ai和 B j B_j Bj的交点上使其同时满足行和列的限制。想要答案最优就需要尽可能地在限制下将最大值为 x x x的行列进行配对。将在点 ( i , j ) (i, j) (i,j)上放数字看作在 A i A_i Ai和 B j B_j Bj之间连一条边,即将问题转化为求二分图的最大匹配, x x x对答案的贡献即为 x ∗ ( a + b − t ) x*(a + b - t) x∗(a+b−t), t t t为二分图的最大匹配。对于那些行列没有配对的 x x x,因为一定有满足条件的解,不妨认为它们都填在某一最大值为 x x x的行 / / /列的格子中。接下来考虑次大值 y y y,发现 y y y和 x x x情况相同,那么对于每一个行列最大值建立二分图求最大匹配后统计答案即可。
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, k, ed[2005][2005];
int num, tong[1000005], ord[1000005];
long long ans;
struct maximum
{
int x, bh;
bool operator < (const maximum & a) const {return x > a.x;}
}r[1000005], c[1000005];
int rnow = 1, cnow = 1;
int rsz, csz, ri[505], ci[505];
bool vis[4005];
int match[4005];
vector<int>adj[4005];
void add(int u, int v)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
void read()
{
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= n; i++)
{
scanf("%d", &r[i].x);
r[i].bh = i;
tong[r[i].x] = 1;
}
sort(r+1, r+n+1);
for(int i = 1; i <= n; i++)
{
scanf("%d", &c[i].x);
c[i].bh = i;
tong[c[i].x] = 1;
}
sort(c+1, c+n+1);
for(int i = 1; i <= m; i++)
{
int u, v;
scanf("%d %d", &u, &v);
ed[u][v] = 1;
}
for(int i = 1e6; i >= 0; i--)
if(tong[i] == 1)
ord[++num] = i;
}
void build(int now)
{
rsz = csz = 0;
for(int i = rnow; i <= n; i++)
{
if(r[i].x == now)
ri[++rsz] = r[i].bh;
else
{
rnow = i;
break;
}
}
for(int i = cnow; i <= n; i++)
{
if(c[i].x == now)
ci[++csz] = c[i].bh;
else
{
cnow = i;
break;
}
}
}
int dfs(int u)
{
for(int i = 0; i < adj[u].size(); i++)
{
int v = adj[u][i];
if(vis[v])
continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[u] = v;
match[v] = u;
return 1;
}
}
return 0;
}
void solve()
{
for(int h = 1; h <= num; h++)
{
build(ord[h]);
if(rsz == 0 || csz == 0)
{
ans += ord[h]*max(rsz, csz);
continue;
}
for(int i = 1; i <= n; i++)
match[i] = 0;
for(int i = 1; i <= rsz; i++)
for(int j = 1; j <= csz; j++)
if(ed[ri[i]][ci[j]] == 1)
add(ri[i], ci[j]+n);
int t = 0;
for(int i = 1; i <= rsz; i++)
{
for(int j = 1; j <= 2*n; j++)
vis[j] = 0;
if(!match[ri[i]])
if(dfs(ri[i]))
t++;
}
ans += ord[h]*(rsz + csz - t);
}
}
int main()
{
read();
solve();
cout << ans;
return 0;
}
E: Math
题目大意
求 n n n以内满足 x 2 + y 2 x^2+y^2 x2+y2能被 x y + 1 xy+1 xy+1整除的 ( x , y ) (x, y) (x,y)对数。 n < = 1 e 18 n<=1e18 n<=1e18, t < = 1 e 5 t<=1e5 t<=1e5。
思路
不妨设 ( x , y ) (x, y) (x,y)满足方程 x 2 + y 2 = k ( x y + 1 ) x^2+y^2 = k(xy+1) x2+y2=k(xy+1),且 x < = y x <= y x<=y,则固定 x x x不动,由韦达定理有 y + y ’ = k x y + y’ = kx y+y’=kx及 y y ’ = x 2 − k yy’ = x^2 - k yy’=x2−k,则 y ’ = k x − y y’ = kx - y y’=kx−y,同时 y ’ < x ( y y ’ < x 2 ) y’< x(yy’ < x^2) y’<x(yy’<x2)。将 y ’ y’ y’代入原方程,易知 y ’ > = 0 y’ >= 0 y’>=0。不妨交换 x x x和 y ’ y’ y’,即 ( y ’ , x ) (y’, x) (y’,x)满足方程。数次递推后会得到最小的 x x x和 y y y,此时有 k x − y = 0 kx - y = 0 kx−y=0(不然会一直递降下去)。代入原方程得 k = x 2 k = x^2 k=x2, y = x 3 y = x^3 y=x3,则最原始的满足条件的数对为 ( x , x 3 ) (x, x^3) (x,x3)。反向递推得下一个 ( x ’ , y ’ ) (x’, y’) (x’,y’)为 ( y , k x ’ − x ) (y, kx’ - x) (y,kx’−x),即 ( x 3 , x 5 − x ) (x^3, x^5 - x) (x3,x5−x)。对每一个 x x x执行数次递推直到 y y y超过 n n n的范围,将每一个 y y y存入数组排序,每一次查询时二分找到大于给定值的第一个下标即可。
代码
#include <bits/stdc++.h>
using namespace std;
long long x, y, k, n, t;
long long ans[5000000], cnt;
void ycl()
{
n = 1e18;
ans[++cnt] = 1;
x = 2;
y = x*x*x;
k = x*x;
while(1)
{
if(y > n)
break;
long long xx = x, yy = y, m;
while(1)
{
ans[++cnt] = yy;
if(__int128(k)*__int128(yy)-__int128(xx) > n)
break;
m = xx, xx = yy, yy = k*yy - m;
}
x++;
y = x*x*x;
k = x*x;
}
sort(ans+1, ans+cnt+1);
}
int main()
{
ycl();
scanf("%lld", &t);
while(t--)
{
scanf("%lld", &n);
printf("%lld\n", upper_bound(ans+1, ans+cnt+1, n)-ans-1);
}
return 0;
}
F: 24dian
题目大意
一张牌上可以是 1 − 13 1-13 1−13任意数字,求用 n n n张牌上的数任意做 + − ∗ / +-*/ +−∗/ 后得到 m m m的合法序列。注意:一个序列是合法的当且仅当这组序列在运算操作中出现了分数。 ( 1 ≤ n ≤ 4 , 1 ≤ m ≤ 1 0 9 ) (1≤n≤4,1≤m≤10^9) (1≤n≤4,1≤m≤109)。
思路
易知如果 n ≤ 3 n ≤ 3 n≤3或者 m > 1 3 4 m > 13^4 m>134一定没有合法序列。当 n = 4 n=4 n=4 时先用 d f s dfs dfs生成 1 − 13 1-13 1−13的全排列,然后枚举对任意两个数做 + − ∗ / +-*/ +−∗/ 操作,递归对结果和剩下的数继续操作,用 f l o o r floor floor函数判断是否出现分数。
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, s, an;
double a[5], eps = 1e-5;
struct answer
{
int a, b, c, d;
}ans[10010];
int check(double x)
{
return fabs(x - floor(x)) > eps;
}
void solve(int step, int f)
{
if(s == 2)
return;
if(step == 1)
{
if(fabs(a[1] - m) < eps)
{
if(f)
s = 1;
else
s = 2;
}
return;
}
double cnt[5];
for(int i = 1; i <= 4; i++)
cnt[i] = a[i];
for(int i = 1; i <= step; i++)
{
for(int j = 1; j <= step; j++)
{
if(i == j)
continue;
for(int k = 1; k <= 4; k++)
{
int w = f;
if(k == 1)
a[i] += a[j];
else if(k == 2)
a[i] -= a[j];
else if(k == 3)
a[i] *= a[j];
else
{
a[i] /= a[j];
if(check(a[i]))
w = 1;
}
a[j] = 19260817;
sort(a+1, a+step+1);
a[step] = 0;
solve(step-1, w);
for(int i = 1; i <= 4; i++)
a[i] = cnt[i];
}
}
}
}
void dfs(int step)
{
if(step == 5)
{
s = 0;
solve(4, 0);
if(s == 1)
{
an++;
ans[an].a = a[1];
ans[an].b = a[2];
ans[an].c = a[3];
ans[an].d = a[4];
}
return;
}
for(int i = a[step-1]; i <= 13; i++)
{
a[step] = i;
dfs(step+1);
}
}
int main()
{
scanf("%d %d", &n, &m);
if(n <= 3 || m > 13*13*13*13)
{
cout << 0;
return 0;
}
a[0] = 1;
dfs(1);
cout << an << endl;
for(int i = 1; i <= an; i++)
printf("%d %d %d %d\n", ans[i].a, ans[i].b, ans[i].c, ans[i].d);
return 0;
}
J: Counting Triangles
题目大意
随机生成任意两点之间边的颜色,求三边同色三角形的个数。 N < = 8000 N<=8000 N<=8000。
思路
注意到一个神奇的性质:每个三角形要么同色,要么有两边同色另一边异色。对于后者,三角形恰有两个异色角,而前者没有异色角。因此异色角数
/
2
/2
/2 即为不符合条件的三角个数。用总数减去即可。复杂度
O
(
N
2
)
O(N^2)
O(N2)。
观察数据规模发现一定是
n
2
n^2
n2算法,思考的时候发现
n
2
n^2
n2只能确定一条边或者两个点,原有思路行不通,这时候就要及时更换思路,去思考同色三角形和异色三角形的不同特征。
代码
#include <bits/stdc++.h>
using namespace std;
namespace GenHelper
{
unsigned z1,z2,z3,z4,b,u;
unsigned get()
{
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
bool read() {
while (!u) u = get();
bool res = u & 1;
u >>= 1; return res;
}
void srand(int x)
{
z1=x;
z2=(~x)^0x233333333U;
z3=x^0x1234598766U;
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
bool edge[8005][8005];
long long c[2], ans;
int main() {
int n, seed;
cin >> n >> seed;
srand(seed);
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
edge[j][i] = edge[i][j] = read();
for(int i = 0; i < n; i++)
{
c[0] = c[1] = 0;
for(int j = 0; j < n; j++)
{
if(i == j)
continue;
c[edge[i][j]]++;
}
ans += c[0]*c[1];
}
long long N = n;
long long all = N*(N-1)*(N-2)/6;
ans = all - ans/2;
printf("%lld", ans);
return 0;
}