2019.9.14 校内交流测试

2019中秋佳节 欢乐赛

T1  残缺棋盘(chessboard.cpp)

 【题目描述】

大佬LDX和FQH一动不动地在棋盘前已经沉默地坐了五个小时。他们全神贯注地盯着每粒棋子。

突然,LDX说:“原则上我是反对在下棋时说话的,但是我现在不得不开口问:现在究竟该谁走下一步棋了?”

FQH说:“谁先走都不重要了,现在的问题是:谁把这个棋盘上的格子损坏了?”

正如图所示,有一正方形棋盘,其边长为2k(1<k<10),其中有一格损坏。现在想用如图中间所示形状的硬纸板将没有坏的所有格子盖起来。而硬纸板不得放入坏格中和棋盘外面。编程输出一种覆盖方案,将共用一块硬纸板的三个格子用相同的数字表示。

 

 

上图所示是k=2的情形,且输出结果不一定和图示方案一致,符合题目要求即可,输出时只需输出数字方阵而不必画出格子线。

【输入格式】

三个整数,即k和坏格子的y坐标和x坐标(注意坏格子的坐标输入顺序)。

【输出格式】

数字方阵,其中坏坐标以数字7表示。

【输入样例】

2 1 1

【输出样例】

7 4 2 2

4 4 4 2

3 4 4 4

3 3 4 4

 

题解

因为边长是 2k  ,所以,一定可以把矩形划分成一个个 2*2 的小方块

在2*2的方块中,也就是k=1时,就很容易确定数字的填法了

 

那么k>1呢???

分治

 

 

中心块开始补 

然后补全就是这样:

 

 

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<cstdlib>
#include<queue>

using namespace std;

typedef long long ll;

inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

int k,x,y;
int pop[15]={1,2,4,8,16,32,64,128,256,512,1024};
int ans[2000][2000];


int check(int lx,int ly,int rx,int ry,int gx,int gy)
{
    if(gx==lx&&gy==ly) return 1;
    if(gx==lx&&gy==ry) return 2;
    if(gx==rx&&gy==ly) return 3;
    if(gx==rx&&gy==ry) return 4;
}


void tian(int lx,int ly,int rx,int ry,int gx,int gy)
{
    int p=check(lx,ly,rx,ry,gx,gy);
    if(p==1){
        ans[rx][ly]=5-p;
        ans[lx][ry]=5-p;
        ans[rx][ry]=5-p;
    }
    if(p==2){
        ans[lx][ly]=5-p;
        ans[rx][ly]=5-p;
        ans[rx][ry]=5-p;
    }
    if(p==3){
        ans[lx][ly]=5-p;
        ans[lx][ry]=5-p;
        ans[rx][ry]=5-p;
    }
    if(p==4){
        ans[lx][ly]=5-p;
        ans[lx][ry]=5-p;
        ans[rx][ly]=5-p;
    }
}

void dfs(int lx,int ly,int rx,int ry,int gx,int gy)
{
    if(ry-ly+1==2) {
        tian(lx, ly, rx, ry, gx, gy);
        return ;
    }

    
    int lon=(ry-ly+1)/2;
    int ax=lx+lon-1,ay=ly+lon-1,
        bx=ax+1,by=ay+1;

    if(gx<=ax&&gy<=ay){
       dfs(lx,ly,ax,ay,gx,gy);
    dfs(lx,by,ax,ry,ax,by);
    dfs(bx,ly,rx,ay,bx,ay);
    dfs(bx,by,rx,ry,bx,by);
    }
    if(gx>ax&&gy<=ay){
        dfs(lx,ly,ax,ay,ax,ay);
    dfs(lx,by,ax,ry,ax,by);
    dfs(bx,ly,rx,ay,gx,gy);
    dfs(bx,by,rx,ry,bx,by);
    }
    if(gx<=ax&&gy>ay){
        dfs(lx,ly,ax,ay,ax,ay);
    dfs(lx,by,ax,ry,gx,gy);
    dfs(bx,ly,rx,ay,bx,ay);
    dfs(bx,by,rx,ry,bx,by);
    }
    if(gx>ax&&gy>ay){
    dfs(lx,ly,ax,ay,ax,ay);
    dfs(lx,by,ax,ry,ax,by);
    dfs(bx,ly,rx,ay,bx,ay);
    dfs(bx,by,rx,ry,gx,gy);
    }
    int m=gx<=ax?ax:bx;
    int n=gy<=ay?ay:by;
    tian(ax,ay,bx,by,m,n);
    return ;
}


int main()
{
    freopen("chessboard.in","r",stdin);
    freopen("chessboard.out","w",stdout);
    k=read();y=read();x=read();
    ans[x][y]=7;

    dfs(1,1,pop[k],pop[k],x,y);
    
    for(int i=1;i<=pop[k];i++){
        for(int j=1;j<=pop[k];j++)
          printf("%d ",ans[i][j]);
        printf("\n");
    }
    
    return 0;
}


 

 

 

 

 T2   (ac.cpp)

AC自动机:

题目背景

YZD在每天学习20小时的勤奋研究下,终于开发出了AC自动机!但是,这台AC自动机有一些bug,比如两个部件之间经常会出一些莫名其妙的问题,所以他想要随时知道被损坏的两个部件之间有多少根电线.

题目描述

AC自动机的结构是一个有着n个节点,2n - 2条边的有向图,

前n-1条边,从部件1连向部件2-n,形成了一颗生成树。

后n-1条边,从部件2-n依次连向部件1.

你需要支持两种操作:查询两个点的最短路和修改一条边的长度。

输入输出格式

输入格式

第一行两个整数n,q,为点的数目和询问的数目;

下面2n-2行,每行三个整数u, v, w, 表示从u到v连一条有向边。

下面q行,每行3个整数k,u,v,若k=1,询问从u到v的最短路, 若k=2,修改第u条边长度为v。

输出格式

对于1询问, 每行输出一个整数,表示最短路的长度

样例

输入

5 3
1 2 3
1 3 4
3 4 5
3 5 6
2 1 1

3 1 2
4 1 3
5 1 4
1 3 2
2 3 -3
1 3 2

输出

5
3

范围

对于40 %的数据,没有2操作。

对于100 %的数据, n,q <= 1e5.

 

题解

spfa TLE 

堆优化的dij TLE 

FLoyd 不仅TLE还WA

 

所以,树链剖分!

 

下面我们看着代码来讲一讲

 

 

 

首先存边 然后存完了

边分两种

      1~n-1 条:以1为根的一棵生成树

n~2n-2 条:点2~n依次直接连回点1

 

我们会得到一颗生成树,对于这棵生成树,

每一个节点都是由序号比它小的节点所指向,也就是这句话的含义:

       >

每一个节点都有一条直连边返回节点1

       >

 

然后我们对这棵树dfs

一颗子树对应dfs序上的一个区间

对于每个点 x 我们记录两个数值 L[x] , r[x]

L[x] 表示 x 的 dfs 序(用dfn表示)(它是第几个被遍历到的)

r[x] 表示回溯到 x 时已经搜了几个点

那么 L[x]和r[x]也就构成了一个区间(同一棵子树的dfs序是连续的)

以x为根的子树构成区间的左dfn和右dfn

所以一颗子树对应dfs序上的一个区间

 

然后我们以dfs序建树

t[k].min = data[x]+f[x]

t[k].data = data[x] 只有k代表的区间是单点,也就是树上一个叶节点的时候记录data

 

对于修改操作分两种:

1.修改n~2n-2的边:

   此时只会影响到一条边,也就是线段树单点修改

   arg传进去标记为1

   线段树修改也就是只需要修改 min 和 tag2

2.修改1~n-1的边:

   此时会影响到 以 修改边的出点 为根的子树中所有点的 dep 值

   也就是区间 [ L[x]  ,  r[x] ]中所有点的 dep 值会受影响

   arg默认标记0

   modify(1,L[x],r[x],修改量)

 

 

对于查询呢?

get操作是将y跳到与x同一深度

如果此时y=x,那么 x 到 y 的路径有 x 一直往下走,走到 y 

不然的话 x 到 y 可以分两种情况:

(1)x 跳到 根节点 ,根节点跳到 y 

 

(2)x 跳到自己的子节点,子节点跳到根,根跳到 y 

 

 

 综合分析也就是,x 先跳到自己子树内的一个点,然后从这个点跳到根节点,再从根节点跳到 y,路径取最小值

 也就是  query(1,L[x],r[x])- pos(1,L[x])+ pos(1,L[y])

 

 

代码

#include <cstdio>
#include <cctype>
#include <vector>
#define ll long long
#include <algorithm>
const ll maxn = 200005;
const ll inf = 1e13;

struct edge{
    ll from, to, w;
    edge(ll c, ll a, ll b):from(c), to(a),w(b){}
    edge(){}
}e[maxn << 1];

ll n, q, cnt, dfn, ans;
ll fa[maxn][20];
ll f[maxn],  //点 i 到 点 1 的距离 ,回去 
   dep[maxn], //点 1 到 点 i 的距离 ,去 
   deep[maxn], //点i的深度 
   l[maxn], //子树k的左儿子编号 
   que[maxn], //dfn序列 
   r[maxn]; //子树k的右儿子编号 

std::vector<ll> to[maxn];  //to[u] u的出边 编号 



void add_edge(ll u, ll v, ll w) {
    e[++cnt] = edge(u, v, w);
    to[u].push_back(cnt);
}



ll read() {
    ll x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
    if(ch == '-') f = -1;
    ch = getchar();
    }
    while(isdigit(ch)) {
    x = x * 10 + ch - '0';
    ch = getchar();
    }
    return x * f;
}



struct seg{
    ll l, r, 
       min, //min(dep)+f 
       dat, // 
       tag1, tag2; //tag1:dep, tag2:f
}t[maxn<<2];



void update(ll k) {
    t[k].min = std::min(t[k<<1].min, t[k<<1|1].min);
}



void pushdown(ll k) {
    if(t[k].l == t[k].r) return;
    
    if(t[k << 1].l == t[k<<1].r) t[k<<1].dat += t[k].tag1;
    
    if(t[k << 1|1].l == t[k<<1|1].r) t[k<<1|1].dat += t[k].tag1;
    
    t[k<<1].min += t[k].tag2 + t[k].tag1;
    t[k<<1|1].min += t[k].tag2 + t[k].tag1;
    
    t[k<<1].tag1 += t[k].tag1;
    t[k<<1].tag2 += t[k].tag2;
    
    t[k<<1|1].tag1 += t[k].tag1;
    t[k<<1|1].tag2 += t[k].tag2;
    
    t[k].tag1 = t[k].tag2 = 0;
}



void build(ll k, ll l, ll r) {
    t[k].l = l, t[k].r = r, t[k].tag1 = t[k].tag2 = 0;
    if(l == r) {
    t[k].min = dep[que[l]] + f[que[l]];
    t[k].dat = dep[que[l]];
    return;
    }
    ll mid = (l + r) >> 1;
    build(k << 1, l, mid);
    build(k << 1|1, mid+1, r);
    update(k);
}



void modify(ll k, ll x, ll y, ll a, ll arg = 0) {
//arg 可以写也可以不写,如果不写就默认0 
    pushdown(k);
    ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
    
    if(x <= l && r <= y) {
      if(arg == 0) {
        t[k].tag1 += a;
        t[k].min += a;
        if(t[k].l == t[k].r) t[k].dat += a;
      }
      else {
        t[k].tag2 += a;
        t[k].min += a;
      }
      return;
    }
    
    if(x <= mid) modify(k << 1, x, y, a, arg);
    if(y > mid) modify(k << 1|1, x, y, a, arg);
    
    update(k);
}

ll pos(ll k, ll p) 
//节点1-->节点 dfs序 为p的节点的距离 
{
    pushdown(k);
    ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
    if(l == r) return t[k].dat;
    if(p <= mid) return pos(k << 1, p);
    else return pos(k << 1|1, p);
}

ll query(ll k, ll x, ll y) 
//节点1-->区间[x,y]中,点的最短距离 
{
    pushdown(k);
    ll l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
    if(x <= l && r <= y) return t[k].min;
    ll ans = inf;
    if(x <= mid) ans = std::min(ans, query(k << 1, x, y));
    if(y > mid) ans = std::min(ans, query(k << 1|1, x, y));
    return ans;
}


void dfs(ll x, ll fa) {
//    一个子树对应dfs序上的一个子区间
    l[x] = ++dfn;
    
    que[dfn] = x;
    
    for(ll i = 0; i < to[x].size(); i++) {
      ll v = e[to[x][i]].to;
      if(v != fa) {
        dep[v] = dep[x] + e[to[x][i]].w;
        deep[v] = deep[x] + 1;
        dfs(v, x);
      }
    }
    
    r[x] = dfn;
}



void pre() {
    scanf("%lld %lld", &n, &q);
    
    for(ll i = 1; i < n; i++) { //生成树连边,存父亲 
    ll u = read(), v = read();
    add_edge(u, v, read());
    fa[v][0] = u;
    }
    
    for(ll i = 1; i < n; i++) { //连边,回去 
    ll u = read(), v = read();
    f[u] = read();
    e[++cnt] = edge(u, v, f[u]);
    }
    
    for(ll i = 1; i <= 18; i++)
      for(ll j = 1; j <= n; j++)
        fa[j][i] = fa[fa[j][i-1]][i-1]; 
    //处理每个点的祖先 
        
    dfs(1, 0);
    
    build(1, 1, dfn);
}



ll get(ll x, ll d) {
    ll D = deep[x];
    ll p = D - d;
    if(p < 0) return -1; //y比x浅 
    ll u = x;
    for(ll i = 18; i >= 0 && p; i--) {
    if((1<<i) <= p) {
        p -= (1<<i);
        u = fa[u][i]; //u往上跳 
    }
    }
    return u;
}

void solve() {
   while(q--) {
    ll k = read(), x = read(), y = read();
    if(k == 2) {
        if(x > (n-1)) {
        modify(1, l[e[x].from], l[e[x].from], y - f[e[x].from], 1);
        f[e[x].from] = y;
        }
        else {
        ll delta = y - e[x].w;
        ll u = e[x].to;
        modify(1, l[u], r[u], delta);
        e[x].w = y;
        }
    }
    else {
        ll k = get(y, deep[x]);
        if(k == x) ans = pos(1, l[y]) - pos(1, l[x]);
        else ans = query(1, l[x], r[x]) - pos(1, l[x]) + pos(1, l[y]);
        printf("%lld\n", ans);
    }
  }
}

int main() {
#ifdef orz
    freopen("input", "r", stdin);
#endif
    pre();
    solve();
}

 

 

 

 

T3   自动AK(ak.cpp)

题目描述

YZD继AC自动机之后,为了更好地A题,他每天学习172309651385613894032916519835028936513785613278490623578136502364983271570863209847928356113785600123984173256134850129466329041623665837012934872130647132805612398472316507613257802471926347213561029830348937721958612302381943821735213504982137412381965123785913260948723981067132865023908472319808565231785012149823147013256193284721096421387561029047123094632578012364890273412350612935623057个小时,在半个月的83279419851635195618290384782396517895620498723046132705652198325632785613204974591235127398023473056130975613750729351761029843732890651785601293847208356103765239018471853610934823747413456021983497239506137056192834729031651780562384759820981347187561780479812657864501293874920651027364912875天里,终于研发出了自动AK机!

但是,YZD的自动AK机目前还只有核心部分,还需要完善。其中,代码检查部分由于只涉及到字符串匹配,YZD不屑于写那么简单的代码。

于是,他把这任务交给了你:

你需要完成三个任务:

任务1:代码可以以一个长为的只含小写字母的字符串组成;现在给定一个长为的模板字符串,也只含有小写字母。现在,你需要求出所有模板串在代码串中出现的位置。

任务2:同任务1,但由于这次YZD需要了解更多信息,你需要求出模板串从代码串的每个位置开始最长出现的多少(就是说,对于代码串的每个位置,你需要求出模板串最长的前缀,使得这个前缀在代码串的这个位置开始出现了)。

任务3:同任务1,但是由于数据缺失,模板字符串里可能会出现’?’字符,它可以看做任何字符(每个‘?’只能看做恰好一个字符)。你需要求出模板串在代码串里所有可能出现的位置。

输入输出格式

输入格式

输入第一行一个数字,表示你要完成哪个任务。

接下来两行分别是代码串和模板串。

输出格式

对于任务1、3,你要输出一行两个数,分别表示出现(或可能出现)了多少次和所有出现的开始位置的异或值。位置从0开始编号。

对于任务2, 你要输出一行4个数a0,a1,a2,a3,ai表示模4余i的所有位置的答案的异或值。位置从0开始。

样例

输入一

1
aabaabaaa
aabaa

输出一

2 3

输入二

2
aabaabaaa
aabaa

输出二

5 1 2 7

输入三

3
aabaabaaa
a??aa

输出三

3 7

样例解释

样例一,模板串在位置0、3出现;

样例二,模板串在九个位置出现的最大长度分别为5,1,0,5,1,0,2,2,1。

样例三,模板串在位置0,3,4出现。

范围

任务1, 2: 代码串和模板串长度.

任务3: 代码串和模板串长度.

 

题解

任务一:kmp板子题

 

任务二:简单来说,就是代码串从每个位置开始往后,作为代码串,和模式串进行匹配,问最大匹配的个数,对于每个位置,都会得到一个值,把这些值,按照 ' 位置 ' 分类,取异或和

 

 

任务三: 没鼓捣明白

神奇的 FFT

求个卷积

设代码串为A,模式串为B

C(i , j)= (Ai - Bj2

若C(i,j)=0证明  Ai , B匹配

QAQ 没听明白 我错啦 耽误您时间了

 

代码

#include <algorithm>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstring>
typedef long long LL;
const int N = 1005000;
char s[N], p[N];
int n, m;
int something[N];
namespace Task1{
  int* const nxt = something;
  void getNxt() {
    nxt[0] = nxt[1] = 0;
    for (int i = 1, j = 0; i < m; ++i) {
      while (j && p[i] != p[j]) j = nxt[j];
      nxt[i + 1] = (p[i] == p[j] ? ++j : 0);
    }
  }
  void solve() {
    int ans1 = 0, ans2 = 0;
    getNxt();
    for (int i = 0, j = 0; i < n; ++i) {
      while (j && p[j] != s[i]) j = nxt[j];
      if (p[j] == s[i] && (++j == m)) {
        ++ans1;
        ans2 ^= i - m + 1;
      }
    }
    printf("%d %d\n", ans1, ans2);
  }
};
namespace Task2{
  int *const f = something;
  void getF() {
    f[0] = m;
    int rmax = 0, ri = 0;
    for (int i = 1; i < m; ++i) {
      f[i] = std::max(0, std::min(f[i - ri], rmax - i));
      while (p[f[i]] == p[i + f[i]]) ++f[i];
      if (i + f[i] > rmax)
        rmax = i + f[ri = i];
    }
  }
  void solve() {
    int ansv[4] = {0, 0, 0, 0};
    getF();
    int rmax = 0, ri = 0;
    for (int i = 0; i < n; ++i) {
      int ans = std::max(0, std::min(rmax - i, f[i - ri]));
      while (i + ans < n && ans < m && s[i + ans] == p[ans]) ++ans;
      if (i + ans > rmax)
        rmax = (ri = i) + ans;
      ansv[i % 4] ^= ans;
    }
    printf("%d %d %d %d\n", ansv[0], ansv[1], ansv[2], ansv[3]);
  }
};
namespace Task3{
  const int N = 400000;
  typedef std::complex<double> Complex;
  const double pi = acos(-1.0);
  Complex PA[N], PB[N];
  LL A[N], B[N], C[N];
  void FFT(Complex *P, int len, int opt) {
    for (int i = 1, j = len >> 1, k; i < len; ++i) {
      if (i < j) std::swap(P[i], P[j]);
      k = len;
      do j ^= (k >>= 1); while (~j & k);
    }
    for (int h = 2; h <= len; h <<= 1) {
      Complex wn = Complex(cos(2 * pi * opt / h), sin(2 * pi * opt / h));
      for (int j = 0; j < len; j += h) {
        Complex w = Complex(1.0, .0);
        for (int k = 0; k < h / 2; ++k) {
          Complex tmp = P[j + k], tmp2 = P[j + k + h / 2] * w;
          P[j + k] = tmp + tmp2;
          P[j + k + h / 2] = tmp - tmp2;
          w *= wn;
        }
      }
    }
    if (opt == -1) {
      for (int i = 0; i < len; ++i)
        P[i] /= len;
    }
  }
  void upd(LL *A, LL *B, LL *C, int n, int m) { //A += B[0..n-1] * C[0..m-1]
    int len = 1;
    while (len < n + m) len <<= 1;
    for (int i = 0; i < n; ++i) PA[i] = B[i];
    for (int i = n; i < len; ++i) PA[i] = .0;
    for (int i = 0; i < m; ++i) PB[i] = C[i];
    for (int i = m; i < len; ++i) PB[i] = .0;
    FFT(PA, len, 1);
    FFT(PB, len, 1);
    for (int i = 0; i < len; ++i)
      PA[i] *= PB[i];
    FFT(PA, len, -1);
    for (int i = 0; i < len; ++i)
      A[i] += (LL)(PA[i].real() + (PA[i].real() > 0 ? 0.5 : -0.5));
  }
  inline LL sqr(LL x) { return x * x; }
  inline LL cube(LL x) { return x * x * x; }
  void solve() {
    for (int i = 0; i < n; ++i)
      B[i] = sqr(s[i] - 'a' + 1);
    for (int i = 0; i < m; ++i)
      C[i] = (p[m - i - 1] == '?' ? 0 : p[m - i - 1] - 'a' + 1);
    upd(A, B, C, n, m);
    for (int i = 0; i < n; ++i)
      B[i] = -2 * (s[i] - 'a' + 1);
    for (int i = 0; i < m; ++i)
      C[i] = sqr(C[i]);
    upd(A, B, C, n, m);
    LL t = 0;
    for (int i = 0; i < m; ++i) if (p[i] != '?')
      t += cube(p[i] - 'a' + 1);
    int ans1 = 0, ans2 = 0;
    for (int i = 0; i <= n - m; ++i) if (A[i + m - 1] == -t) {
      ++ans1;
      ans2 ^= i;
    }
    printf("%d %d\n", ans1, ans2);
  }
}
int main() {
  int task;
  scanf("%d%s%s", &task, s, p);
  n = strlen(s);
  m = strlen(p);
  if (task == 1) Task1::solve();
  if (task == 2) Task2::solve();
  if (task == 3) Task3::solve();
  return 0;
}

 

posted @ 2019-09-17 16:55  晔子  阅读(169)  评论(0编辑  收藏  举报