1. 程式人生 > 實用技巧 >2020牛客暑期多校訓練第二場部分

2020牛客暑期多校訓練第二場部分

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}} \]

然後將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[j]) ^ 2 + (y - y[j]) ^ 2 = x ^ 2 + y ^ 2 \]

\[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]} \]

\[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]} \]

先判斷一下分母是否為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 ;
}
/*
*/


剩下幾題咱也不會