2020 noip

Pots: 101

  • T160'

  • T2:16'

  • T3:0'

  • T4:25'

T1

[NOIP2020] 排水系统

拓扑排序板子 & 高精度

(long~long) 没开全从 90' 挂到 60',从此发誓以后坚决 define int long long

solution

没啥好说的

注意:(lcm) 先除后乘,以及分数的处理

__int128 水过的代码

code

/*
work by:Ariel_
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll __int128
using namespace std;
const int N = 1e5 + 5;
const int M = 5e5 + 5;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
void print(ll n) {
    if(n > 9) print(n / 10);
    putchar(n % 10 + 48);
} 
ll gcd(int a, int b){return (b) ? gcd(b, a % b) : a;}
ll lcm(int a, int b){return a / gcd(a, b) * b;}
int n, m, d[N], ind[N], tot, ans[N];
queue<int> q;
struct edge{int v, nxt;}e[M];
int cnt, head[N];
void add_edge(int u, int v){
  e[++cnt] = (edge){v, head[u]};
  head[u] = cnt;	
} 
struct node{
   ll p, q;
   node(){
   	  p = 0, q = 1;
   } 
   node operator * (const int &rhs) const{
        node res;
        res.p = p, res.q = q * rhs;
        ll g = gcd(res.p, res.q);
        res.p /= g, res.q /= g;
        return res;
   }
   node operator + (const node &rhs)const{
        node res;
        res.q = lcm(q, rhs.q);
        res.p += p * (res.q / q);
        res.p += rhs.p * (res.q / rhs.q);
        ll g = gcd(res.p, res.q);
        res.p /= g, res.q /= g;
        return res;
   }
}a[N];
void topu(){
   for(int i = 1; i <= n; i++) if(!ind[i]) q.push(i);
   while(!q.empty()){
   	  int u = q.front();q.pop();
   	  if(d[u]) a[u] = a[u] * d[u];//分水 
   	  for(int i = head[u]; i; i = e[i].nxt){
   	         int v = e[i].v;
			 a[v] = a[v] + a[u], ind[v]--;
			 if(!ind[v])q.push(v);  	    
	   }
   }
}
signed main(){
   //freopen("water.in","r",stdin);
   //freopen("water.out","w",stdout);
   n = read(), m = read();
   for(int i = 1; i <= n; i++){
   	   d[i] = read();
   	  if(!d[i]) ans[++tot] = i;;
   	  for(int j = 1, v; j <= d[i]; j++){
   	  	   v = read(), add_edge(i, v);
   	  	   ind[v]++;
	   }	 
   }
   for(int i = 1; i <= m; i++) a[i].p = 1;//进水口 
   topu();
   for(int i = 1; i <= tot; i++)print(a[ans[i]].p), putchar(' '),        print(a[ans[i]].q), putchar('
');
   puts("");
   return 0; 
} 

T2

[NOIP2020] 字符串匹配

扩展 (KMP) ? 我不会/kk

(hash) 能卡卡常能跑过去 /se?!

solution

16pots: 纯暴力,(O(n^5)), 枚举 (a) 串和 (b) 结束的位置 (i, j) 以及 (c) 串开始的位置 (k) ,根据题目条件暴力判断,是否合法 代码

48 pots: 优化上面的暴力,上面每次判断都要统计一遍 (a) 串中出现奇数次的字母的个数和(c) 串中出现奇数次数的字母的个数。我们可以先预处理前缀中出现奇数次数的字母个数的前缀和以及后缀中出现奇数次数的字母个数的后缀和,那么 (a) , (c) 串中出现奇数次数的字母的个数就可以(O(1)) 求了,时间复杂度 (O(n^4)) 代码

84~100 pots:

开一个桶 (vis) 记录每种出现奇数次的字母出现次数有几个前缀,枚举 (i) 的时候把桶的前缀和累加到对应位置,前缀和暴力维护即可。

循环节用 (hash) 判断

时间复杂度 (O(t(nlogn+26n)))

卡常AC代码 (code)

/*
work by:Ariel_
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define ull unsigned long long
using namespace std;
const int N = 1050005;
const ull base = 131;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int T, n, ans, vis[31], l[N], opt[30];
short sum1[N], sum2[N], a[N], b[N], c[N];
ull H[N], p[N];
char s[N];
void clear(){
  	memset(a, 0, sizeof a);
	memset(b, 0, sizeof b);
	memset(opt, 0, sizeof opt);
	memset(vis, 0, sizeof vis);
	memset(sum1, 0, sizeof sum1);
	memset(sum2, 0, sizeof sum2);
} 
signed main(){
   T = read();
   p[0] = 1;
   for(int i = 1; i <= N; i++) p[i] = p[i - 1] * base;
   while(T--){
   	  scanf("%s", s + 1);
   	  n = strlen(s + 1);
	  clear();
   	  ans = 0;
   	  for(int i = 1; i <= n; i++) {
   	  	 c[i] = s[i] - 'a' + 1;
   	     opt[c[i]] ^= 1;
   	     if(opt[c[i]]) sum1[i] = sum1[i - 1] + 1;
   	     else sum1[i] = sum1[i - 1] - 1;
   	     a[i] = sum1[i];//前缀 
	   }
	  memset(opt, 0, sizeof opt);
	  for(int i = n; i >= 1; i--){
	  	 opt[c[i]] ^= 1;
	  	 if(opt[c[i]]) sum2[i] = sum2[i + 1] + 1;
	  	 else sum2[i] = sum2[i + 1] - 1;
	  	 b[i] = sum2[i];//后缀 
	  } 
	  for(int i = 1; i <= n; i++) H[i] = base * H[i - 1] + 1ull * c[i];//hash 
	  for(int i = 2; i <= n; i++){//|AB|的长度 
	  	 for(int j = a[i - 1]; j <= 26; j++) vis[j]++;//比这个 F(i) 大的后缀都可以直接累加到后面去(最难受的地方) 
		 for(int j = i; j < n && H[i] == H[j] - H[j - i] * p[i]; j += i) ans += vis[b[j + 1]]; 
	  }
	  printf("%lld
", ans); 
   }
   puts("");
   return 0;
}

100 pots: (O(n)) 扩展 (KMP)

……

T3

[NOIP2020] 移球游戏

一道神仙构造体= =

题目描述:

(n + 1) 个柱子,(n) 个柱子上有 (m) 个不同颜色的球,一共有 (n) 种颜色,最终目标是使得这每个柱子上的 (m) 球颜色相同

  • (x) 号柱子上至少有一个球;

  • (y) 号柱子上至多有 (m - 1) 个球;

  • 只能将 (x) 号柱子最上方的球移到 (y) 号柱子的最上方。

求一种操作次数在 (82000) 之内完成目标的合法方案

(2 leq n leq 50) (2leq mleq400)

考试一点思路也没有,读题,走人/kk

solution

构造

枚举颜色,将这个颜色的球都移到一个柱子上

把这个颜色都求都变为 1, 其他的都变为 0

问题就转化成把所有的 1 的移到同一列上

过程:

  • 构造全 0 列

  • 构造全 1 列

  • 特判 (n = 2)

方法 参考 QwQcOrZ

主要我懒得弄图了 = =

code

/*
work by:Ariel_
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N	= 55;
const int M = 405;
const int MAXN = 820005;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int n, m, L[MAXN], R[MAXN], CNT;
int a[N][M], cnt[N], tot[N], p[N];
 
int top(int x){return a[x][cnt[x]];}//顶层球的颜色

int count(int x, int y){//x柱子上颜色为y的球的数量 
   int res = 0;
   for(int i = 1; i <= m; i++) res += (a[x][i] == y);
   return res;
} 
void move(int x,int y){
	++CNT;
	L[CNT] = x, R[CNT] = y;
	a[y][++cnt[y]] = a[x][cnt[x]];
	cnt[x]--; 
}
int main(){
   n = read(), m = read();
   for(int i = 1; i <= n; i++){
   	  cnt[i] = m;//每个柱子上球的个数 
   	  for(int j = 1; j <= m; j++) a[i][j] = read();
   }
   cnt[n + 1] = 0;
   for(int i = 1; i <= n + 1; i++) p[i] = i;//位置 
   for(int now = n; now >= 3; now--){//枚举颜色(同样也是现在所需要考虑的柱子的个数"n")  
	  int tmp = count(p[1], now);
	  //构造全 0 列 
   	  for(int i = 1; i <= tmp; i++) move(p[now], p[now + 1]);//step.1.1 
	  
	  for(int i = 1; i <= m; i++)//step.1.2 
	   if(top(p[1]) == now) move(p[1], p[now]);
	   else move(p[1], p[now + 1]);
	  
	  for(int i = 1; i <= m - tmp; i++)move(p[now + 1], p[1]);//step.1.3
	  
	  for(int i = 1; i <= m; i++)//step.1.(3,4) 
	   if(top(p[2]) == now || cnt[p[1]] == m) move(p[2], p[now + 1]);
	   else move(p[2], p[1]);
	  //构造全 1 列 
	  swap(p[1], p[now]);//交换之后 p[now] 是全 0 列,p[now + 1] 空的 
	  swap(p[2], p[now + 1]);
	  for(int k = 1; k < now; k++){
	  	  tmp = count(p[k], now);
	  	  for(int i = 1; i <= tmp; i++) move(p[now], p[now + 1]);//step.2.1
	  	  for(int i = 1; i <= m; i++)//step.2.1 
			if(top(p[k]) == now) move(p[k], p[now]);
			else move(p[k], p[now + 1]); 
		  swap(p[k], p[now + 1]), swap(p[k], p[now]);
       }
	   for(int i = 1; i < now; i++) while(top(p[i]) == now)move(p[i], p[now + 1]);
	   for(int i = 1; i < now; i++) while(cnt[p[i]] < m) move(p[now], p[i]);
    }
	  //特判 n = 2
	  int tmp = count(p[1], 1);
	  for(int i = 1; i <= tmp; i++) move(p[2], p[3]);
	  for(int i = 1; i <= m; i++)//step.3.1 
	    if(top(p[1]) == 1) move(p[1], p[2]);
	    else move(p[1], p[3]);
	  for(int i = 1; i <= tmp; i++) move(p[2], p[1]);//step.3.2  
	  for(int i = 1; i <= m - tmp; i++) move(p[3], p[1]);
	  while(cnt[p[3]])  move(p[3], p[2]);//step.3.3
	  for(int i = 1; i <= m - tmp; i++) move(p[1], p[3]);
	   for(int i = 1; i <= m; i++)
	   if(top(p[2]) == 1) move(p[2], p[1]);
	   else move(p[2], p[3]);  
   printf("%d
", CNT);
   for(int i = 1; i <= CNT; i++){
   	  printf("%d %d
", L[i], R[i]);
   }
   puts("");
   return 0;
}
原文地址:https://www.cnblogs.com/Arielzz/p/14853742.html