test20230817考试总结(思维训练)

前言#

这一次思维训练,我感觉到了巨佬们的强大,也感受到了我与巨佬们的差距。

A B C D E F

A#

Problem#

给定一个长度为 n 的序列 a(ai{0,1})0 为空,1 为非空。

现在有一种操作:将 i 上的 1i+2i2 上的 0 互换,但 i+1i1 必须为 1

求序列可能达到的状态数。

Solve#

显然,两个连续的 1 是可以捆绑在一起计算的,偶数个连续的 1 可以看作若干个两个连续的 1 .

这样只要考虑”落单的”奇数个 1 就行了。

对于一个落单的 1,它可能会在某个时刻被两个连续的 1 推着走,像这样:

红色的 1 为之前落单的 1

但是这样就会变被很复杂。

换一种思路,不是 11 推着 1 走,而是 111 互换了位置。这样,落单的 1 的贡献完全取决与 11 的贡献。换句话说,把落单的 1 连着位置一起去掉与原答案等效。

所以只要统计捆绑起来的 11 在对应位置上的排列组合就行了。

设两个连续的 1 的个数为 x,落单的 1 的个数为 y,序列长度为 n,则答案为 Cnxyx.

Code#

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 998244353

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 2e5 + 10;

int inv[N], fac[N], n;

char a[N]; 

int qpow(int a, int b) {
  int res = 1;
  while(b) {
    if(b & 1) res = (res % mod * a % mod) % mod;
    a = (a % mod * a % mod) % mod, b >>= 1; 
  }
  return res;
}

int C(int m, int n) {
  return fac[n] * inv[m] % mod * inv[n-m] % mod;
}

void solve() {
  scanf("%d%s", &n, a + 1);
  int num1 = 0, num2 = 0, i = 1;
  for (; i <= n; i++) {
    if(a[i] == '1' && a[i+1] == '1') num1++, i++;
    else if(a[i] == '0') num2++;
  }
  cout << C(num1, num1 + num2) << '\n';
  return ;
} 

signed main() {
  fac[0] = inv[0] = fac[1] = inv[1] = 1;
  For(i,2,N-10) {
    fac[i] = (fac[i-1] % mod * i % mod) % mod;
    inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
  }
  For(i,2,N-10) {
    inv[i] = inv[i-1] * inv[i] % mod; 
  }
  int Q = read();
  while(Q--) {
    solve(); 
  } 
  return 0;
}

时间复杂度 O(n)(如果没有线性求逆元,那么时间复杂度为 O(nlogn))。

B#

Problem#

给定一个 0L 的数轴,有 n 个奶牛在数轴上,位置为 xi,速度为 di{1,1},体重为 wi[1,103]

有两种事件:

  • 奶牛 i 移动到了 0L 点,则停止运动,并做 wi 的体重贡献;
  • 当奶牛 i 和 奶牛 j 占据了相同点,则互换速度。奶牛可能在一个非整数点相遇;

T 等于停止移动的奶牛(由于到达两个牛棚之一)的重量之和至少等于所有奶牛的重量之和的一半的最早时刻。请求出在时刻 0T 之间发生的奶牛相遇次数。

Solve#

考虑将问题化简:两个奶牛相遇时,对穿并且互换体重。这样与原问题等价,但更好处理。

考虑一个奶牛 i 到达终点时的重量贡献。由于速度一定,不会出现奶牛追及问题。这样在进行多轮体重互换后,对奶牛 i 的体重有影响的奶牛只有一个。

假设这个奶牛的为 j,则奶牛 j 的方向与奶牛 i 的方向相反(这是必然的)。此时奶牛 i 到达终点使的贡献为 wjd,其中 d[i+1,j] 中与奶牛 i 方向相同的奶牛数量。(不会证明,手玩出来是这样)。

计算出每一个奶牛到牛棚的距离,由于奶牛的运动方向恒定,所以对于每一个奶牛都有唯一的终点。然后对于每一个先到终点的奶牛,计算该奶牛的贡献。这里是 O(nlogn) 的(因为可以用优先队列优化模拟过程,每一次取的奶牛肯定是最先到的,即离终点距离最短的。每一次从优先队列里取出离终点距离最短的奶牛计算)。

这样就可以计算出 T,然后在枚举所有奶牛,对于方向为左的奶牛,去寻找其他的方向为右的奶牛对冲。如果能冲到,则计数器加一。

总时间复杂度 O(nlogn).

Code#

#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 1e5 + 10;

struct Node {
  int w, x, d;
} a[N];

struct node {
  int id, x, d;
  bool operator < (const node &a) const {
    return a.x < x;
  } 
};

int n, l, now, s1[N], s2[N], laL, laR, Q[N], ans, sum, T;

priority_queue<node> q;

bool cmp(Node x, Node y) {
  return x.x < y.x;
}

signed main() {
  n = read(), l = read();
  For(i,1,n) a[i] = (Node) {read(), read(), read()};
  sort(a + 1, a + n + 1, cmp);
  For(i,1,n) {
    q.push((node){i, (a[i].d == 1 ? l - a[i].x : a[i].x), a[i].d});
    s1[i] = s1[i-1] + (a[i].d == 1), s2[i] = s2[i-1] + (a[i].d == -1);
    sum += a[i].w;
  }
  FOR(i,n,1) {
    if(a[i].d == 1) laL = i;
  }
  For(i,1,n) {
    if(a[i].d == -1) laR = i;
  }
  while(!q.empty()) {
    int id = q.top().id, x = q.top().x, d = q.top().d;
    q.pop();
    int dis = (d == 1 ? s1[laR] - s1[id] : s2[id-1] - s2[laL-1]);
    T = x;
    if(d == 1) {
      now += a[laR - dis].w;
    } else {
      now += a[laL + dis].w;
    }
    if(now * 2 >= sum) break;
  }
  int h = 1, t = 0;
  For(i,1,n) {
    if(a[i].d == 1) Q[++t] = i;
    else {
      while(h <= t && a[i].x - a[Q[h]].x > 2 * T) h++;
      ans += t - h + 1;
    }
  }
  cout << ans << '\n';
  return 0;
}

C#

Problem#

有一个 n 个节点的树,现在可以将树改造,删去一条边,再加上一条边,问有多少个节点可能成为数树的重心(即其子数大小都不大于节点总数的一半)。

Solve#

一眼换根 dp

每一次我的操作肯定是想要树更胖,即根节点更有可能成为重心。

考虑求出这棵树改哪个子树上的边能使其更胖。

首先这棵子树一定要合法,并且它的大小要最大。

注意 dp 的时候要算父辈节点的子树(因为是以不同的树为根而言)。

然后考虑换根 dp,最后判断边加边以后能否使其成为重心。

当然还有一个细节,定义 以 u 为根,儿子为 v,当最胖的子树就为子树 v 本身时,断边加边操作无效,则需启用次大子树操作。所以维护 dp 的最大次大值即可。

Code#

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 1e6 + 10;

struct Node {
  int v, nx;
} e[N << 1];

int n, h[N], tot, siz[N], Max[N][2], rt, cut[N], ans[N];

void add(int u, int v) {
  e[++tot].v = v, e[tot].nx = h[u], h[u] = tot;
}

void dfs1(int x, int fa) {
  siz[x] = 1;
  bool f = 1;
  for (int i = h[x]; i; i = e[i].nx) {
    int y = e[i].v;
    if(y == fa) continue;
    dfs1(y, x);
    siz[x] += siz[y];
    if(siz[y] > n / 2) f = 0;
  } 
  if(n - siz[x] > n / 2) f = 0;
  if(f) rt = x;
}

void dfs2(int x, int fa) {
  siz[x] = 1;
  for (int i = h[x]; i; i = e[i].nx) {
    int y = e[i].v;
    if(y == fa) continue;
    dfs2(y, x);
    siz[x] += siz[y];
    if(siz[y] > n / 2) continue;
    if(siz[y] > Max[x][0]) Max[x][1] = Max[x][0], Max[x][0] = siz[y];
    else if(siz[y] > Max[x][1]) Max[x][1] = siz[y];   
  }
}

void dfs3(int x, int fa, int maxn) {
  cut[x] = maxn;
  for (int i = h[x]; i; i = e[i].nx) {
    int y = e[i].v;
    if(y == fa) continue;
    if(n - siz[x] <= n / 2) maxn = max(maxn, n - siz[x]);
    if(Max[x][0] == siz[y]) dfs3(y, x, max(maxn, Max[x][1]));
    else dfs3(y, x, max(maxn, Max[x][0]));
  } 
  if(n - siz[x] - cut[x] <= n / 2 || x == rt) ans[x] = 1;
}

signed main() {
  n = read();
  For(i,1,n-1) {
    int u = read(), v = read();
    add(u, v); add(v, u);
  }
  dfs1(1, 0);
  dfs2(rt, 0);
  dfs3(rt, 0, 0);
  For(i,1,n) cout << ans[i] << ' ';
  return 0;
}

D#

Problem#

给定一张有向简单图,给定邻接矩阵,求长度为 k 的路径条数,答案对 109+7 取模。

Solve#

不会啊~

Code#

不会啊~

E#

Problem#

对于一个长度为 len 的序列,它的答案定义为最小划分的组数(以使得每个组中的数两两乘积为平方数)。

给你一个长度为 n(1n5000) 的数组 ai(108ai108)。对于其所有的子串,第 k 个输出的数是答案为 k 的子串的数量。

Solve#

可以证明,ab=n2,cb=m2,则 ac=k2(a,b,c,n,m,kZ)

证明:

a=p1A1p2A2p3A3pxAkb=p1B1p2B2p3B3pxBkc=p1C1p2C2p3C3pxCkA1+B10(mod2)A2+B20(mod2)A3+B30(mod2)Ak+Bk0(mod2)C1+B10(mod2)C2+B20(mod2)C3+B30(mod2)Ck+Bk0(mod2)A1+C10(mod2)A2+C20(mod2)A3+C30(mod2)Ak+Ck0(mod2)

所以 ab=n2,cb=m2,则 ac=k2(a,b,c,n,m,kZ)

有了这一个结论,直接暴力将 ai×aj 插入并查集,然后暴力枚举每一个区间,统计区间内集合数。

记得开 long long。

Code#

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 5e3 + 10;

int n, x[N], f[N], num[N], ans[N];

int find(int x) {
  return (x == f[x] ? x : f[x] = find(f[x]));
}

void merge(int x, int y) {
  f[find(x)] = find(y);
}

signed main() {
  n = read();
  For(i,1,n) x[i] = read(), f[i] = i;
  For(i,1,n) {
    For(j,i+1,n) {
      if(x[i] * x[j] > 0) {
        int tmp = sqrt(x[i] * x[j]);
        if(tmp * tmp == x[i] * x[j]) {
          merge(i, j); 
        }
      }
    }
  }
  int tot = 0;
  For(i,1,n) {
    tot = 0;
    memset(num, 0, sizeof num);
    For(j,i,n) {
      if(!x[j]) ans[max(1ll, tot)]++;
      else {
        if(!num[find(j)]) {
          num[find(j)] = 1; tot++;
        }
        ans[tot]++; 
      }
    }
  }
  For(i,1,n) cout << ans[i] << ' '; 
  return 0;
}

F#

Problem#

给定一个正整数序列 a,计算有多少个正整数数列 b ,满足:

  1. 1biai,i[1,n]
  2. bibi+1,i[1,n)

答案对 998244353 取模。

Solve#

显然,在决策第 i 项时,前 i1 项不会受到影响,无后效性,考虑 dp

dpi,j 表示前 i 项,第 i 项为 j 的合法方案数。

递推式为:

dpi,j=1kai1,kjdpi1,k

答案为 k[1,an]dpi,k

发现 i 那一维可以滚动数组。(滚动数组用完一维就要清空一维)(不给递推式了)

但是时空双炸,然后就推进不下去了。

换一种思路,考虑容斥,称满足 bi=bi+1(ii<n)i 为关键点。则答案为关键点为 0{bn} 的数量,假设有 i 个关键点时的 {bn} 数量为 ri,则有:

rn=i=0n1(1)iri

发现对于长度为 n{bn},至少有 k 个关键点的一组 {bn} 其实是将整个数组分成了连续 nk 段,每段的 bi 一定相同,再次考虑 dp

fi,0/1 表示在 b1,bi 这一段排列,分成连续的偶数段或奇数段的方案数,每段的 bi 一定相同。

则有:

fi,0/1=j=1ifj1,0/1(mink=jiak)

这里的 mink=jiak 的含义为 bj,,bi 这些数只能是同一个数,所以是取最小值。

转移时间复杂度为 O(n2) ,不过可以使用单调栈优化。

找到从 i 向左看第一个小于 ai 的数 ax,则有:

fi,0/1=fx,0/1+aij=x+1ifj1,0/1

因为 ax 为从 i 向左看第一个比 ai 小的数,所以有 mink=jiakmink=jxak 相等,所以这些 j 的贡献等价于 fx,0/1

由于 ax+1,ax+1,,ai1 都比 ai 大,所以对于任意的 j(j[x+1,i]),都有 mink=jiak=ai,这些 j 的答案贡献之和为 aij=x+1ifj1,0/1

这些操作可以通过单调栈来实现。

边界:f0,0=1,f0,1=0。时间复杂度 O(n)

Code#

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 998244353

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 2e5 + 10;

int n, a[N], stk[N], f[N][2], s[N][2], top;

signed main() {
  n = read();
  For(i,1,n) a[i] = read();
  f[0][0] = s[0][0] = 1;
  For(i,1,n) {
    while(top && a[stk[top]] >= a[i]) top--;
    f[i][0] = ((top == 0 ? 0 : f[stk[top]][0]) % mod + (s[i-1][1] - (top == 0 ? 0 : s[stk[top]-1][1]) + mod) * a[i]) % mod;
    f[i][1] = ((top == 0 ? 0 : f[stk[top]][1]) % mod + (s[i-1][0] - (top == 0 ? 0 : s[stk[top]-1][0]) + mod) * a[i]) % mod;
    s[i][0] = (s[i-1][0] + f[i][0]) % mod;
    s[i][1] = (s[i-1][1] + f[i][1]) % mod;
    stk[++top] = i;
  }
  cout << ((n & 1 ? mod - 1 : 1) * (f[n][0] - f[n][1] + mod) % mod) % mod; 
  return 0;
} 

作者:Daniel-yao

出处:https://www.cnblogs.com/Daniel-yao/p/17640335.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Daniel_yzy  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示