G - Isosceles Trapezium
二维平面, N个点,坐标Xi,Yi, 权重Ci
选4个点, 形成 等腰梯形, 问选的4个点最大权重和
限制
N 1000
Xi,Yi [-1e9,1e9]
Ci [1,1e9]
无重点
3s
1024
我的思路
有点计算几何
N的样子,像是N^2的做法
如果是暴力找三个点, 确定平行边,那么剩下一个点就自然确定了, 这样的话是 N^3 log(N)
换个想法, 按对称轴来找
如果是垂于对称轴的一点,则找对称轴最远的两个点
这样 N^2 的对称轴, 其中相等的里面 按照垂点相同的最大的,找不同的两组就行了??
代码
https://atcoder.jp/contests/abc220/submissions/33799130
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include <bits/stdc++.h> using namespace std;
typedef long long ll; #define MOD 1000000007 #define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) #define pb push_back
ll read(){ll r;scanf("%lld",&r);return r;}
ll gcd(ll a,ll b){ a = abs(a); b = abs(b); while(b!= 0) tie(a,b) = make_pair(b,a%b); return a; } const ll INF = 0x3f3f3f3f3f3f3f3f; array<ll,3> xyv[1010]; map<tuple<ll,ll,ll>, vector<pair<int,int> > > cx;
void addp(int i,int j){ auto [x0,y0,v0] = xyv[i]; auto [x1,y1,v1] = xyv[j]; ll ky = 2*(y1-y0); ll kx = -2*(x1-x0); ll k = (x1-x0)*(x1+x0) + (y1-y0)*(y1+y0); ll g = gcd(k,gcd(ky,kx)); ky /= g; kx /= g; k /= g; if(ky < 0){ ky = -ky; kx = -kx; k = -k; }else if(ky == 0 && kx < 0){ kx = -kx; k = -k; } cx[{ky,kx,k}].push_back({i,j}); }
int main(){ int n = read(); rep(i,0,n){ int x = read(); int y = read(); int v = read(); xyv[i] = {x,y,v}; } rep(i,0,n) rep(j,i+1,n) addp(i,j); ll ans = -1; for(auto [_,vec]:cx){ auto center = [=](const pair<int,int>&ij){ auto [i0,j0] = ij; auto [x0,y0,v0] = xyv[i0]; auto [x1,y1,v1] = xyv[j0]; return make_pair(x0+x1,y0+y1); }; sort(vec.begin(),vec.end(), [=](const auto &ij0,const auto &ij1){ return center(ij0) < center(ij1); }); ll lastmax = -INF; ll cur = -INF; rep(i,0,vec.size()){ if(i == 0 || center(vec[i]) != center(vec[i-1])){ lastmax = max(lastmax,cur); cur = 0; } auto [i0,j0] = vec[i]; cur = max(cur, xyv[i0][2] + xyv[j0][2]); if(lastmax != -INF){ ans = max(ans, lastmax + cur); } } } printf("%lld\n",ans); return 0; }
|
H - Security Camera
N 点, M 边
选定一些点, 让边(至少一个点上有被选定的)的数量是偶数个
求合法方案数
限制
N 40
无重边,自环
2s
1024mb
我的思路
感觉题面就是个朴素的图论
40 呢, 对应边就是780
估计是个边平方~ 3次方 左右的算法, 或者点的5次方?
思路正向就是考虑局部可行方案加上插头状态
逆向就是 所有减去存在未选择的 做容斥
点数量40, 2^40 = 1099511627776
如果, 是一个一个安装的, 那么考虑对于个数的影响
增量是 相邻未安装的和
而对于这个连接出的点,相邻未安装的奇偶性发生颠倒
题解
2^20 = 1048576
折半
把拆成两个点集合S,T
L1[S,s] =
点集S的子集s 被选了, 覆盖的边数的奇偶性
L2[S,T,s] =
点集T中, 连向S\s的数量是奇数的点集? (因为偶数的话,首先不被s选,其次不论在T中是否被选不影响奇偶性
R[T,t] =
点集T的子集t被选了,覆盖的两端属于T的边的奇偶性
因为对于每个选中状态, 可以枚举剩下所有点, 所以 可以$O(|S|2^{|S|})$ 暴力算完
那么对于答案有贡献的
$L_1[s] \oplus ((\text{popcount} (L_2[s] & t) ) &1) \oplus R[t] = 0$
意义 s得到的奇数偶,t内部奇偶,和t向S\s的奇偶 = 最终奇偶
中间这玩意怪怪的,虽然很长意义也就是L2[s] & t
的1的个数的奇偶性
像个办法把右侧合并一下
$F[S,T,s] = \sum_{t \subset T} ((\text{popcount} (L_2[s] \& t) ) \& 1) \oplus R[t] $
注意到 求和部分,奇数贡献1, 偶数贡献0, 所以这里是对于给定s,在T的子集中, 让上述表达式贡献1的个数
那么贡献0的个数就是 $2^{|T|} - F[S,T,s]$
如果能求出来, 那么对于每个$s$, 有$L1[s]$ 的奇偶性, 直接加上对应贡献即可
问题变成是如何求出F[S,T,s]
这里记$t’ = L2[s]$, 这样一个s唯一对应一个t'
, 但t'
可能有多个s
映射过来
记作$G[T,t’] = \sum_{t \subset T} ((\text{popcount} (t’ \& t) ) \&1) \oplus R[t] $
这样有个好处是,不再关心S
和s
, 只用管T
中的即可
注意到 $FWHT$的变换公式是
$fwht[a]_ i = \sum_{(\text{popcount}(i \& j) \bmod 2 = 0}a_j - \sum_{(\text{popcount}(i\& j) \bmod 2 = 1}a_j$
对于给定 i
一个具体的j
左侧为0时, 原式子贡献是 R[j], 而fwht贡献是 a[j]
左侧为1时, 原式子贡献是 R[j]^1, 而fwht贡献是 -a[j]
如果让a[j] = R[j], 那么
左侧为0时, 原式子贡献是 0 , 而fwht贡献是 0
左侧为0时, 原式子贡献是 1 , 而fwht贡献是 1
左侧为1时, 原式子贡献是 0^1, 而fwht贡献是 -0
左侧为1时, 原式子贡献是 1^1, 而fwht贡献是 -1
左侧为0和为1各占一半, 总贡献会少掉$2^{|T|-1}$
加上即可?
另一个做法
所有边变成”有向”, 小点连出到大点
f[i][j][k] =
前i个点, 未覆盖的边的两端都在前i的边数为j(奇1/偶0), 一些未来不选的会影响未覆盖边的奇偶性的点的方案数
i+1
选 f[i][j][k]
贡献 f[i+1][j][k高(i+2)位]
i+1
不选 f[i][j][k]
贡献 f[i+1][j^(i+1是否在k中)][(k高(i+2)位) ^ (i+1 连出的边) ]
很神奇的是, 这样每个点对于每个上个状态最多分支出两个状态
那么前一半最多2^20
个状态
而状态低i
位都是0
, 所以后面的一半也是最多2^20
个状态
所以复杂度也是
n 2^{n/2}
从一定程度上也有meet-in-middle 的感觉,而没有了fwt的需要
代码
https://atcoder.jp/contests/abc220/submissions/33847519
1.7s 快超时了, 为什么有人6ms 啊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <bits/stdc++.h> using namespace std;
typedef long long ll; #define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
ll read(){ll r;scanf("%lld",&r);return r;}
ll g[50];
unordered_map<ll,ll>f[50][2]; int main() { int n = read(); int m = read(); rep(i,0,m){ int x = read() - 1; int y = read() - 1; if(x > y) swap(x,y); g[x] |= 1ll<<y; } f[0][0][0]=1; f[0][0][g[0]]++; rep(i,0,n-1) rep(j,0,2) { for(auto [mask,cnt]:f[i][j]) { ll bit = (mask >> (i+1)) & 1; f[i+1][j][mask^(bit<<(i+1))]+=cnt; f[i+1][j^bit][mask^(bit<<(i+1))^g[i+1]] += cnt; } } ll ans=0; for(auto [_,cnt]:f[n-1][m&1]) ans += cnt; printf("%lld",ans); return 0; }
|
总结
G
没啥难的
简单的计算几何,排序,自定义排序
H
一个是40的一半是20, 2^20 是可以范围内的
另一个是拆的时候,可以按点拆分,一半是有点就包含,另一半是需要两端都属于集合
参考
官方题解
csdn 逆天峰 H