2020牛客多校训练第二场
出题数 2 — D题(真水题) 和 F题(滑动窗口)
D、Duration
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef long long ll ;
const double esp = 1e-6 , pi = acos(-1) ;
typedef pair<int , int> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 1e9 + 7;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
int main()
{
int ah , am , as , bh , bm , bs ;
scanf("%d:%d:%d" , &ah , &am , &as) ;
scanf("%d:%d:%d" , &bh , &bm , &bs) ;
ll a = ah * 3600 + am * 60 + as ;
ll b = bh * 3600 + bm * 60 + bs ;
cout << abs(a - b) << endl ;
return 0 ;
}
/*
*/
F Fake Maxpooling
整个矩阵很好求,然后要求求每个k * k矩阵的最大值,我先预处理出来每个k * 1的最大值,然后按照上图滑动窗口的时候,左面丢出一个k * 1, 右面多出一个k * 1, 直接按照滑动窗口的模式就可以,然后这是一行的, 然后枚举每行, 做滑动窗口就行了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef long long ll ;
const double esp = 1e-6 , pi = acos(-1) ;
typedef pair<int , int> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 1e9 + 7;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
int a[5010][5010] , ans = 0 , maxn[5010][5010] ;
int q[5010] ;
int main()
{
int n = in() , m = in() , k = in() ;
for(int i = 0; i < n ;i ++ ) {
for(int j = 0; j < m ;j ++ ) {
a[i][j] = 1ll * (i + 1) * (j + 1) / __gcd(i+ 1 ,1 + j) ;
}
}
for(int j = 0; j < m ;j ++ ) {
int hh = 0 , tt = -1 ;
for(int i = 0; i < n ;i ++ ) {
if(i - k + 1 > q[hh]) ++ hh ;
while(hh <= tt && a[i ][j] >= a[q[tt]][j]) -- tt ;
q[++ tt] = i ;
if(i + 1 >= k) maxn[i - k + 1][j] = a[q[hh]][j] ;
}
}
ll ans = 0 ;
for(int i = 0 ;i < n ;i ++ ) {
int hh = 0 , tt = -1 ;
for(int j = 0 ;j < m ;j ++ ) {
if(j - k + 1 > q[hh]) ++ hh ;
while(hh <= tt && maxn[i][j] >= maxn[i][q[tt]]) -- tt ;
q[++ tt] = j ;
if(j + 1 >= k) {
ans += 1ll * maxn[i][q[hh]] ;
}
}
}
cout << ans << endl ;
return 0 ;
}
/*
*/
补题系列
C Cover the Tree
一条链 , 两个端点, 并且这两个端点都是度为1的点 , 要求最小的数量,就是(n + 1) / 2, 如果n是偶数的话, 就两两配对,如果是奇数的话,再多一条链。那么怎么配对呢。这个看的大佬的代码是这样写的。
边(u , v) , u < v, 最终的图也就是上图 , 就直接小标号的叶子节点匹配大标号的叶子节点
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef long long ll ;
const double esp = 1e-6 , pi = acos(-1) ;
typedef pair<int , int> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 1e9 + 7;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
int deg[N] , res[N];
int main()
{
int n = in() ;
for(int i = 1 , a ,b ;i < n ;i ++ ) {
a = in() , b = in() ;
deg[a] ++ , deg[b] ++ ;
}
int ans = 0 ;
for(int i = 1; i <= n ;i ++ )
if(deg[i] == 1)
res[++ ans] = i ;
cout << (ans + 1) / 2 << endl ;
for(int i = 1; i <= (ans + 1) / 2;i ++ )
cout << res[i] << " " << res[ans / 2 + i] << endl ;
return 0 ;
}
/*
*/
J.Just Shuffle
此题相当于一个单位置换群e,通过置换 P置换群 k次得到一个置换群A
e
∗
P
k
=
A
e
∗
P
=
A
k
−
1
P
=
A
k
−
1
e * P^k = A\\ e*P = A^{k^{-1}}\\P = A^{k^{-1}}
e∗Pk=Ae∗P=Ak−1P=Ak−1 然后将i和a[i]连接起来,就会得到一些环,每个环就是一个置换群,也就是每个A,然后求A的逆元就行了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef long long ll ;
const double esp = 1e-6 , pi = acos(-1) ;
typedef pair<int , int> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 1e9 + 7;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
int vis[N] , a[N] , b[N] , res[N] ;
int main()
{
int n = in() , k = in() ;
for(int i = 1; i <= n ;i ++ ) a[i] = in() ;
for(int i = 1; i <= n ;i ++ ) {
if(vis[i]) continue ;
int pos = i ;
vector<int> ans ;
while(!vis[pos]) ans.push_back(pos) , vis[pos] = 1 , pos = a[pos] ;
int size = ans.size() ;
int t = 0 ;
while(t < size) {
if(1ll * t * k % size == 1) break ;
t ++ ;
}
for(int i = 0 ;i < size ;i ++ )
res[ans[i]] = ans[(i + t) % size] ;
}
for(int i = 1; i <= n ;i ++ ) cout << res[i] << " " ;
puts("") ;
return 0 ;
}
/*
*/
B、Boundary
解法一:算圆心坐标
枚举两点 , 加上原点,总共三点,三点定圆
(
x
−
x
[
i
]
)
2
+
(
y
−
y
[
i
]
)
2
=
x
2
+
y
2
(x - x[i]) ^ 2 + (y - y[i]) ^ 2 = x ^ 2 + y ^ 2
(x−x[i])2+(y−y[i])2=x2+y2
(
x
−
x
[
j
]
)
2
+
(
y
−
y
[
j
]
)
2
=
x
2
+
y
2
(x - x[j]) ^ 2 + (y - y[j]) ^ 2 = x ^ 2 + y ^ 2
(x−x[j])2+(y−y[j])2=x2+y2
x
=
b
[
j
]
∗
(
x
[
i
]
2
+
y
[
i
]
2
)
−
b
[
i
]
∗
(
x
[
j
]
2
+
y
[
j
]
2
)
2
x
[
i
]
∗
y
[
j
]
−
2
∗
x
[
j
]
∗
y
[
i
]
x = \frac{b[j] * (x[i] ^ 2 + y[i] ^ 2) - b[i] * (x[j] ^ 2 + y[j] ^ 2)}{2x[i] * y[j] - 2 * x[j] * y[i]}
x=2x[i]∗y[j]−2∗x[j]∗y[i]b[j]∗(x[i]2+y[i]2)−b[i]∗(x[j]2+y[j]2)
y
=
x
[
j
]
∗
(
x
[
i
]
2
+
y
[
i
]
2
)
−
x
[
i
]
∗
(
x
[
j
]
2
+
y
[
j
]
2
)
2
x
[
j
]
∗
y
[
i
]
−
2
∗
x
[
i
]
∗
y
[
j
]
y = \frac{x[j] * (x[i] ^ 2 + y[i] ^ 2) - x[i] * (x[j] ^ 2 + y[j] ^ 2)}{2x[j] * y[i] - 2 * x[i] * y[j]}
y=2x[j]∗y[i]−2∗x[i]∗y[j]x[j]∗(x[i]2+y[i]2)−x[i]∗(x[j]2+y[j]2)
先判断一下分母是否为0 , 记得用long long
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef long long ll ;
const double esp = 1e-7 , pi = acos(-1) ;
typedef pair<int , int> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 1e9 + 7;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
ll x[N] , y[N] ;
double get(int i){
return 1ll * x[i] * x[i] + y[i] * y[i] ;
}
vector<pair<double , double>> ans ;
int main()
{
int n = in() ;
for(int i = 1; i <= n ;i ++ ) x[i] = in() , y[i] = in() ;
int res = 0 ;
for(int i = 1; i <= n ;i ++ ) {
ans.clear() ;
for(int j = 1 ; j <= n ;j ++ ) {
ll yy = x[j] * get(i) - x[i] * get(j) ;
ll t = 2 * x[j] * y[i] - 2 * x[i] * y[j] ;
if(t == 0) continue ;
ll xx = y[j] * get(i) - y[i] * get(j) ;
ans.push_back({(double)xx / (-t) , (double) yy / t}) ;
}
sort(ans.begin() , ans.end()) ;
int cnt = 1 , maxn = 0 ;
for(int i = 1 ; i < ans.size() ;i ++ )
{
if(fabs(ans[i].x - ans[i - 1].x) < esp && fabs(ans[i].y - ans[i - 1].y) < esp) cnt ++;
else cnt = 1 ;
maxn = max(maxn , cnt) ;
}
res = max(res , maxn + 1) ;
}
cout << res << endl ;
return 0 ;
}
/*
*/
解法二:算夹角
这两个角一定相同,同时为了避免重复,在上图中二选一,也就是选择一个判断标准,op向量*oa向量 < 0, 也可以选择大于0 , 或者选择ap * ao, 然后算夹角acos(dot(ap , ao) / (len(ap) * len(ao))) , 最后随便统计一下, 不过精度好象要调到1e-9, 在1e-8都是不能完全通过的(也可能写的程序不太对)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef long long ll ;
const double esp = 1e-7, pi = acos(-1) ;
typedef pair<double , double> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 1e9 + 7;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
PII a[N] ;
double dot(PII a , PII b){
return a.x * b.x + a.y * b.y ;
}
double cross(PII a , PII b){
return a.x * b.y - a.y * b.x ;
}
double c[N] ;
double len(PII a){
return sqrt(a.x * a.x + a.y * a.y) ;
}
double calc(PII a , PII b){
double lena = len(a) , lenb = len(b) ;
return acos(dot(a , b) / (lena * lenb)) ;
}
int main()
{
int n = in() ;
for(int i = 1; i <= n ;i ++ ) cin >> a[i].x >> a[i].y ;
int ans = 0 ;
for(int i = 1; i <= n ;i ++ ) {
int cnt = 0 ;
for(int j = 1; j <= n ;j ++ ) {
PII op = a[i] , oa = a[j] ;
PII ap = {a[j].x - a[i].x , a[j].y - a[i].y} , ao = {-a[j].x ,- a[j].y} ;
if(cross(op , oa) < 0) c[++ cnt] = calc(ap , ao) ;
}
sort(c + 1 , c + cnt + 1) ;
int maxn = 0 , res = 0 ;
double t = 0 ;
for(int i = 1; i <= cnt ;i ++ ) {
if(fabs(c[i] - t) < esp) res ++ ;
else res = 1 , t = c[i] ;
maxn = max(maxn , res) ;
}
ans = max(ans , maxn) ;
}
cout << ans + 1 << endl ;
return 0 ;
}
/*
*/
A.All with Pairs
看到这题的第一个想法就是先预处理出来每个字符串的所有后缀,用map存起来,然后再枚举每个字符串的前缀,判断有多少个之前map存储的hash值个数,但是发现题目要求是要求最大,而不是求所有的,上面的这个做法可以将所有符合前缀等于后缀的贡献求出来, 但不能求最大的。然后发现一个小小的性质,kmp算法next数组的经典应用
上图两个字符串匹配中蓝色竖杠之间的是前缀等于后缀的部分,但是是要求最大的,我们接着往下面匹配
突然后面这个绿色之间的匹配的一部分比蓝色部分更大,也就是说当前这个答案更优,那我们就不要前面那个,但是发现
对于黑色曲线,我们发现,1、2、3、4、5号线段字符串部分全部都是相同的,也就是1 和 5是相同的,这不就是kmp里面如果枚举到5部分突然失配了, 会跳到1部分嘛,对于这个性质应用到当前题目上面就是-----题目要求最大,我们就把所有前缀后缀相同的部分都直接算答案,不过面对上图这个情况,我们可以再kmp即将失配的时候,也就是i失配的时候,将next[i]部分的贡献减掉,即h[ne[j]] -= h[j], 这样就在把所有贡献都加上 的同时, 减掉对于当前贡献来说前面贡献比较小的部分,就是最大的贡献 , 即题目所求
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <map>
#include <list>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <stack>
#include <set>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std ;
#define ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
#define x first
#define y second
typedef unsigned long long ll ;
const double esp = 1e-6 , pi = acos(-1) ;
typedef pair<int , int> PII ;
const int N = 1e6 + 10 , INF = 0x3f3f3f3f , mod = 998244353 , base = 131;
ll in()
{
ll x = 0 , f = 1 ;
char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = -1 ; ch = getchar() ;}
while(isdigit(ch)) x = x * 10 + ch - 48 , ch = getchar() ;
return x * f ;
}
unordered_map<ll , int> mp ;
void Hash(string s){
ll res = 0 , p = 1 ;
for(int i = s.size() - 1; i >= 0 ;i --) {
res += p * s[i] ;
p *= base ;
mp[res] ++ ;
}
return ;
}
string s[N] ;
int h[N] , ne[N] ;
void get(string s){
for(int i = 2, j = 0 ;i < s.size() ;i ++ ) {
while(j && s[i] != s[j + 1]) j = ne[j] ;
if(s[i] == s[j + 1]) j ++ ;
ne[i] = j ;
}
return ;
}
int main()
{
int n ;
cin >> n ;
for(int i = 1; i <= n ;i ++ ) {
cin >> s[i] ;
Hash(s[i]) ;
}
ll ans = 0 ;
for(int i = 1; i <= n ;i ++ ) {
ll res = 0 ;
for(int j = 0 ;j < s[i].size() ;j ++ ) {
res = res * base + s[i][j] ;
h[j + 1] = mp[res] ;
}
s[i] = " " + s[i] ;
get(s[i]) ;
for(int j = 1 ; j < s[i].size() ;j ++ )
h[ne[j]] -= h[j] ;
for(int j = 1; j < s[i].size() ;j ++ )
ans = (ans + 1ll * h[j] * j % mod * j % mod) % mod ;
}
cout << ans << endl ;
return 0 ;
}
/*
*/
剩下几题咱也不会