2018.3-4月 雅礼集训题目选做

看看已经完成了多少道题

14

day1

escape

题意

给你一颗树,然后A点在1号点,所有点都有人,每一秒A和所有人都可以移动一格,A可以往树下走,其他人往A方向走,A与人相遇只有在点上或边上遇见才叫相遇,相遇后人消失,问A走到每个点碰到的人,n <=10^5; ai <= 1000

分析

首先A走下来,有些人是永远碰不到A的,假设A的一开始的深度为0,A去到一个点x,那么一个点碰到A点当且仅当 这个点i的(dep[i]+1)/2的祖先在A到x的路径上

然后对于每个点倍增统计到那个点上,然后搜一遍下来就好了

代码

#include 
using namespace std;
const int N = 200010;
inline int read()
{
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node{int x,y,next;}edge[N]; int len,first[N];
void ins(int x,int y){len++; edge[len].x=x; edge[len].y=y; edge[len].next=first[x]; first[x]=len;}

int n,a[N],b[N];

int dep[N],fa[N][25];

void dfs(int x,int f)
{
  for(int k=first[x];k!=-1;k=edge[k].next)
  {
    int y=edge[k].y;
    if(y==f) continue;
    dep[y]=dep[x]+1; fa[y][0]=x; dfs(y,x);
  }
}

int ans[N];

void dfs2(int x,int f,int s)
{
  ans[x] = s + b[x];
  for(int k=first[x];k!=-1;k=edge[k].next)
  {
    int y=edge[k].y;
    if(y==f) continue;
    dfs2(y,x,s+b[x]);
  }
}

int main()
{
  freopen("escape.in","r",stdin);
  freopen("escape.out","w",stdout);

  n=read(); for(int i=1;i<=n;i++) a[i]=read(); len=0; memset(first,-1,sizeof(first));
  for(int i=1;iint x=read(); int y=read(); ins(x,y); ins(y,x);}
  dep[1]=0; dfs(1,0);

  for(int j=1;j<=20;j++) for(int i=1;i<=n;i++) fa[i][j] = fa[fa[i][j-1]][j-1];
  for(int i=1;i<=n;i++)
  {
    int deep = dep[i] - (dep[i]+1)/2; int x=i;
    for(int j=20;j>=0;j--) if(deep >= (1<x=fa[x][j],deep-=(1<x] += a[i]; // printf("%d %d\n",i,x);
  }

  dfs2(1,0,0);
  for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
  return 0;
}

machine

题意


fk[i]=(d|ifk1[d])%998244353 f k [ i ] = ( ∑ d | i f k − 1 [ d ] ) % 998244353

给你初始的f[0][i]和长度n,m个询问,每次给你k和i,求上式
n<=10^5 m<=1000 0<=f[0][i]<998244353,k<=10^5,x<=n

分析

看到998244353就先写个NTT
首先我们很快的分析一波性质
后面显然是和1进行狄利克雷卷积,1卷积后的狄利克雷卷积满足交换律结合律,同时也是一个积性函数

第一个想法,把1的狄利克雷卷积给倍增,然后log提出来,乘的时候要nlogn的时间,总的时间复杂度应该是

O(mnlognlogk) O ( m n l o g n l o g k )

这样显然过不了,我们考虑从积性函数这一点入手
发现这里我们只要做出p和p^q次方,然后剩下的就是积性函数乘一乘就好了
找找规律

q=1 q=2 q=3 q=4 q=5
k=1 1 1 1 1 1
k=2 1 2 3 4 5
k=3 1 3 6 10 20

其实就是只和q有关,和p没有关系,这样每一行的每一个,就是上面的前缀和
还有一个规律就是,每一项都是左边+上面,这样让我们想起了一个东西
对就是组合数,总结一下规律,就有表中的g[k][q]

g[k][q]=(q+k1q) g [ k ] [ q ] = ( q + k − 1 q )

然后我们就可以用g求出f了

f[k][x]=d|xa[d]calc(xd) f [ k ] [ x ] = ∑ d | x a [ d ] ∗ c a l c ( x d )

calc(x=pq11pq22...pqoo)=i=1og[k][qi] c a l c ( x = p 1 q 1 p 2 q 2 . . . p o q o ) = ∏ i = 1 o g [ k ] [ q i ]

过程有点繁琐,但是熟练的选手还是可以很快出来的
预处理组合数即可,时间复杂度

O(mnlogn) O ( m n l o g n )

代码

#include 
#define ll long long
#define C(i,j) (fac[(i)] * inv[(j)] % mod * inv[(i)-(j)] % mod)
using namespace std;
const ll N = 200010;
const ll mod = 998244353;
inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

ll jcp[N][20]; ll a[N];

ll fac[N],inv[N];

bool v[N]; ll prime[N],pri=0,minx[N];

void get_prime(ll n)
{
  memset(v,1,sizeof(v)); v[0] = v[1] = 0; pri=0;
  for(ll i=2;i<=n;i++)
  {
    if(v[i]) prime[++pri] = i,minx[i] = i;
    for(ll j=1;(j<=pri) && (i*prime[j]<=n);j++)
    {
      v[i*prime[j]]=0; minx[i*prime[j]] = prime[j];
      if(i%prime[j]==0) break;
    }
  }
}

ll calc(ll k,ll x)
{
  ll s=1; ll lst=minx[x]; ll p=0;
  while(x!=1)
  {
    while(minx[x] == lst && x!=1) p++,x=x/minx[x];
    s = s * C(p+k-1,p) % mod;
    if(x!=1) lst=minx[x],p=0;
  }
  return s;
}

int main()
{
  freopen("machine.in","r",stdin);
  freopen("machine.out","w",stdout);

  ll n = read(); get_prime(2e5);
  for(ll i=1;i<=n;i++) a[i] = read();
  fac[0] = 1; for(ll i=1;i<=(ll)2e5;i++) fac[i] = fac[i-1] * i % mod;
  inv[0] = inv[1] = 1; for(ll i=2;i<=(ll)2e5;i++) inv[i] = (mod - mod/i) * inv[mod%i] % mod;
  for(ll i=1;i<=(ll)2e5;i++) inv[i] = inv[i-1] * inv[i] % mod;

  // printf("%lld\n",calc(3,1000));
  ll q = read();
  while(q--)
  {
    ll k=read(); ll x=read(); ll ans = 0;
    for(ll i=1;i*i<=x;i++) if(x%i==0)
    {
      if(i*i==x)
      {
        ans = (ans + a[i] * calc(k,i) % mod) % mod;
      }
      else
      {
        ans = (ans + a[i] * calc(k,x/i) % mod) % mod;
        ans = (ans + a[x/i] * calc(k,i) % mod) % mod;

        /*printf("%lld %lld %lld\n",k,x/i,a[i]);
        printf("%lld %lld %lld\n",k,i,a[x/i]);*/
      }
    }
    printf("%lld\n",ans);
  }
  return 0;
}

maze

题意

这是一道交互题

给你一颗二叉树,每个点连着3条边,每条边的颜色分别是0,1,2,根是出口
你可以调用两个函数

int query() 表示到出口的距离
void move(x)表示从当前点走颜色为x的边

你要走到出口,且询问次数在T之内,移动次数小于10^6次方
让你完成一个函数

void findpath(int initialDeep, int T);

initalDeep表示初始深度

对于100% 的数据满足initialDeep <=10^5; T >= 51000

分析

我们先找一个naive的做法,记录从哪条边上来,这条边就不可能是了,最初的点试2次,然后之后每个点平均要试1.5次,就可以得到T>=150000的分

考虑再优化,我们肯定是随便走(当然从哪条边走上来的肯定那条边就不用管),走一段路再询问一遍,每段路径假设走两步,这样的路径肯定是往上走一段,然后走下来,当然往上走一段和往下走一段的长度可能为0

每次最坏情况下是走2步,只上去1步
还有就是走2步,上去2步,平均询问一遍,上去1.5步

然后我们再考虑一下,把这种情况变成走多步就好了,我走了6步,很容易得出期望步数大约为2

代码

#include 
#include "maze.h"

#define MOVE(x) if(move(x)) return ;
#define qry() query()

int col(int c1,int c2)
{
  if(c1 > c2){int t=c1; c1=c2; c2=t;}
  if(c1==0 && c2==1) return 2;
  if(c1==0 && c2==2) return 1;
  if(c1==1 && c2==2) return 0;
}

int col_rand(int c)
{
  // srand(time(0));
  int op=rand()%2;
  int a,b; if(c==0) a=1,b=2;
  else if(c==1) a=0,b=2;
  else if(c==2) a=0,b=1;
  if(op==0) return a;
  else return b;
}

int a[10],alen=0;

void findpath(int initialDeep, int T)
{
  int dep = initialDeep; int sl,c;
  MOVE(0); int c1 = qry(); MOVE(0);
  MOVE(1); int c2 = qry(); MOVE(1);
  if(c1 == dep-1) sl = 0;
  else if(c2 == dep-1) sl = 1;
  else sl = 2;
  MOVE(sl); dep--; //printf("%d\n",qry());

  while(1)
  {
    alen = 0; a[alen] = sl;
    for(int i=1;i<=6;i++)
    {
      a[i] = col_rand(a[i-1]);
      MOVE(a[i]);
    }
    int nowDep = qry();
    int deep = nowDep - dep;
    if(deep == 6)
    {
      for(int i=6;i>=1;i--){MOVE(a[i]) }
      sl = col(sl,a[1]);
      MOVE(sl)
      dep = dep - 1;
    }
    else if(deep == 4)
    {
      for(int i=6;i>=2;i--){MOVE(a[i]) }
      sl = col(a[1],a[2]);
      MOVE(sl)
      dep = dep - 2;
    }
    else if(deep == 2)
    {
      for(int i=6;i>=3;i--){MOVE(a[i]) }
      sl = col(a[2],a[3]);
      MOVE(sl)
      dep = dep - 3;
    }
    else if(deep == 0)
    {
      for(int i=6;i>=4;i--){MOVE(a[i]) }
      sl = col(a[3],a[4]);
      MOVE(sl)
      dep = dep - 4;
    }
    else if(deep == -2)
    {
      for(int i=6;i>=5;i--){MOVE(a[i]) }
      sl = col(a[4],a[5]);
      MOVE(sl)
      dep = dep - 5;
    }
    else if(deep == -4)
    {
      for(int i=6;i>=6;i--){MOVE(a[i]) }
      sl = col(a[5],a[6]);
      MOVE(sl)
      dep = dep - 6;
    }
    else sl = a[6],dep = dep - 6;

  }
}

day2

arg(bzoj 3591最长上升子序列)

题意

给出一个长度为m 的序列A, 请你求出有多少种1…n 的排列, 满足A 是它的一个LIS.

分析

这道题做了几遍了,我们把这些数给压缩一下,考虑之前LIS是怎么求的,开一个栈,如果一个元素在栈内,可能会被后面替换掉,那么在栈中的元素就是1,在栈外的就是2,没入栈的是0,然后用bfs来dp一遍就好了

题目中有一个固定的上升子序列,那么就按顺序插入,然后状态是答案,当且仅当这个状态在栈中的元素的个数是原题题目LIS的长度,还有所有元素要不在栈中,要不在栈外

代码

#include 
#define ll long long
using namespace std;

const ll N = 17;

inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

ll n,k,a[N],pre[N],bin[N]; ll f[15000000];

queueq; ll nex[N];

int main()
{
  freopen("arg.in","r",stdin);
  freopen("arg.out","w",stdout);
  n=read(); k=read(); for(ll i=0;i1;
  ll lst=read()-1; for(ll i=2;i<=k;i++){ll x=read()-1; pre[x] = lst; lst=x;}
  q.push(0); memset(f,0,sizeof(f)); f[0] = 1;
  bin[0] = 1; for(ll i=1;i<=n;i++) bin[i] = bin[i-1] * 3; ll ans = 0;
  while(!q.empty())
  {
    ll x=q.front();

    nex[n]=n; for(ll i=n-1;i>=0;i--) if(x/bin[i]%3 == 1) nex[i] = i; else nex[i] = nex[i+1];  

    ll s=0; for(ll i=0;iif(x/bin[i]%3 == 1) s++;
    ll cnt=0; for(ll i=0;iif(x/bin[i]%3) cnt++;
    if((x/bin[lst]%3) && (s==k) && (cnt==n)) ans+=f[x];

    for(ll i=0;i//Add
    {
      ll ss=s; ll p = x/bin[i]%3; if(p) continue;
      if(pre[i]!=-1)
      {
        ll q = x/bin[pre[i]]%3;
        if(!q) continue;
      }
      ll sta = x + bin[i];
      if(nex[i]else ss++;
      if(ss<=k)
      {
        // printf("%lld -> %lld\n",x,sta);
        if(!f[sta]) q.push(sta); 
        f[sta]+=f[x];
      }
    }q.pop();
  }
  return printf("%lld\n",ans),0;
}

bsh

题意

给定一个正n 边形及其三角剖分, 共2n-3 条边(n 条多边形的边和n-3 条对角线), 每条边的长度为1.
共q 次询问, 每次询问给定两个点, 求它们的最短距离.

分析

我们可以用分治写这道题,维护每次到分治端点的距离,但是考虑到太恶心,我太菜了,就没写,口服一番


cti

题意

有一个n*m 的地图, 地图上的每一个位置可以是空地, 炮塔或是敌人. 你需要操纵炮塔消灭敌人.
对于每个炮塔都有一个它可以瞄准的方向, 你需要在它的瞄准方向上确定一个它的攻击位置,
当然也可以不进行攻击. 一旦一个位置被攻击, 则在这个位置上的所有敌人都会被消灭.
保证对于任意一个炮塔, 它所有可能的攻击位置上不存在另外一个炮塔.
定义炮弹的运行轨迹为炮弹的起点和终点覆盖的区域. 你需要求出一种方案, 使得没有两条炮弹轨迹相交.

分析

一般这种题我们考虑用流来做

因为射出来是一条线段,我们考虑用链构图,然后用最小割,割掉的边代表要选

对于攻击方向上下的炮台,我们这样建
源点->炮台->射出的第一个位置->射出的第二个位置….->汇点

其中位置前的边权为这个位置的数字,但是因为这样是割掉最小的,我们要最大的,就套路一波把数字变成一个大数-这个数字,我用的是1e8-a[i][j]

因为我们有限制,两条路径不能交叉,而且一行之内的炮台是互不干扰的,因为题目所说,炮台不会出现在别人的攻击范围之内,我们考虑如何限制,这也是这道题为什么用最小割比较好的原因。

我们把左右的炮台反着建,然后连边就好了
源点->射出的最后一个位置->射出的倒数第二个位置->…->炮台->汇点

然后对应相交的地方连边

这样是为什么呢,如果两个都割射出位置比较远的点,必然这个图还联通,自己画画就好

代码

#include 
#define ll long long
#define pb push_back
using namespace std;
const ll N = 1000010;
const ll inf = 1e13;

inline ll read()
{
  char ch=getchar(); ll p=0; ll f=1;
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node{ll x,y,c,next;}edge[N]; ll len,first[N];
void ins(ll x,ll y,ll c)
{
  len++; edge[len].x=x; edge[len].y=y; edge[len].c=c; edge[len].next=first[x]; first[x]=len;
  len++; edge[len].x=y; edge[len].y=x; edge[len].c=0; edge[len].next=first[y]; first[y]=len;
  //printf("%lld %lld %lld\n",x,y,c);
}

ll dep[N]; queueq; ll s,d;
bool bfs()
{
  memset(dep,0,sizeof(dep)); dep[s] = 1; 
  while(!q.empty()) q.pop(); q.push(s);
  while(!q.empty())
  {
    ll x=q.front();
    for(ll k=first[x];k!=-1;k=edge[k].next)
    {
      ll y = edge[k].y;
      if(dep[y]==0 && edge[k].c)
      {
        dep[y] = dep[x] + 1;
        q.push(y);
      }
    }
    q.pop();
  }
  return dep[d] > 0;
}
ll dfs(ll x,ll flow)
{
  if(x==d) return flow;
  ll delta = 0;
  for(ll k=first[x];k!=-1;k=edge[k].next)
  {
    ll y = edge[k].y;
    if(flow > delta && dep[y] == dep[x]+1 && edge[k].c)
    {
      ll minf = dfs(y,min(flow-delta,edge[k].c));
      edge[k].c-=minf; edge[k^1].c+=minf;
      delta+=minf;
    }
  }
  if(delta==0) dep[x]=0;
  return delta;
}

ll a[110][110];

vector v[110][110];

int main()
{
  freopen("cti.in","r",stdin);
  freopen("cti.out","w",stdout);

  len=1; memset(first,-1,sizeof(first)); ll tot = 0;
  ll n = read(); ll m = read();
  for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) a[i][j] = read(); s=1; d=2; tot=2;

  for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) if(a[i][j] <= -1 && a[i][j] >= -2)
  {
    tot++; ins(tot,d,1e8);
    if(a[i][j] == -1)
    {
      for(ll k=i-1;k>=1;k--)
      {
        v[k][j].pb(tot); tot++; ins(tot,tot-1,1e8-a[k][j]);
      }
      ins(s,tot,inf); //printf("...\n");
    }
    else
    {
      for(ll k=i+1;k<=n;k++)
      {
        v[k][j].pb(tot); tot++; ins(tot,tot-1,1e8-a[k][j]);
      }
      ins(s,tot,inf); //printf("....\n");
    }
  }

  for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) if(a[i][j] <= -3 && a[i][j] >= -4)
  {
    tot++; ins(s,tot,1e8);
    if(a[i][j] == -3)
    {
      for(ll k=j-1;k>=1;k--)
      {
        for(ll l=0;l1,tot,1e8-a[i][k]);
      }
      ins(tot,d,inf); //printf(".\n");
    }
    else
    {
      for(ll k=j+1;k<=m;k++)
      {
        for(ll l=0;l1,tot,1e8-a[i][k]);
      }
      ins(tot,d,inf); //printf("..\n");
    }
  }


  ll ans = 0;
  while(bfs()) ans-=dfs(s,inf);
  while(ans < 0) ans+=1e8;
  return printf("%lld\n",ans),0;
}

day3

xiz

题意

两个字符串S和T,然后T在S中匹配当且仅当T是S的子串并且T在S中的对应位置转化相同
也就是
3 1 3与1 2 1匹配
字符集大小为c
n,m,c<=10^6

分析

这道题好像之前见过类似的,只要找到与前一个位置的距离
可能与前面的一个位置的距离大于当前串的距离,这个位置就变成没有出现过的了,可以用kmp或者hash来匹配

代码

#include 
#define pb push_back
using namespace std;
const int N = 1000010;
inline int read()
{
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

int a[N],b[N],wa[N],wb[N];

int cnt[N];

int fail[N],nx[N];

vector<int>ans;

int main()
{
  freopen("xiz.in","r",stdin);
  freopen("xiz.out","w",stdout);

  int t=read(); int s=read();
  while(t--)
  {
    int n = read(); int m = read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=m;i++) b[i]=read();

    for(int i=1;i<=s;i++) cnt[i] = 0;
    for(int i=1;i<=n;i++)
    {
      if(!cnt[a[i]]) wa[i] = 0;
      else wa[i] = i - cnt[a[i]];
      cnt[a[i]]=i;
    }

    for(int i=1;i<=s;i++) cnt[i] = 0;
    for(int i=1;i<=m;i++)
    {
      if(!cnt[b[i]]) wb[i] = 0;
      else wb[i] = i - cnt[b[i]];
      cnt[b[i]]=i;
    }

    for(int i=1;i<=n;i++) fail[i] = nx[i] = 0;

    int p=0; fail[1] = 0; int c,d;
    for(int i=2;i<=m;i++)
    {
      while(p)
      {
        if(p+1>m) p=fail[p];
        c = wb[p+1]; d = wb[i];
        if(d>p) d=0;
        if(c==d) break;
        p=fail[p];
      }
      c = wb[p+1]; d = wb[i];
      if(d>p) d=0;
      if(c==d) p++; 
      fail[i] = p; 
    }

    p=0;
    for(int i=1;i<=n;i++)
    {
      while(p)
      {
        if(p+1>m) p=fail[p];
        c = wb[p+1]; d = wa[i];
        if(d>p) d=0;
        if(c==d) break;
        p=fail[p];
      }
      c = wb[p+1]; d = wa[i];
      if(d>p) d=0;
      if(c==d) p++;
      nx[i] = p;
    }

    ans.clear(); for(int i=1;i<=n;i++) if(nx[i] == m) ans.pb(i-m+1);
    printf("%d\n",ans.size());
    for(int i=0;iprintf("%d%c",ans[i]," \n"[i==ans.size()-1]);
  }


  return 0;
}

yja

题意

在平面上找n 个点, 要求这n 个点离原点的距离分别为r1; r2, rn. 最大化这n 个点构成的
凸包面积, 凸包上的点的顺序任意.

分析

枚举点的顺序,对于角度,我们设一设,有

g(θ1,θ2,...,θn) g ( θ 1 , θ 2 , . . . , θ n )

f(θ1,θ2,...,θn)=i=1nriri+1sinθ2 f ( θ 1 , θ 2 , . . . , θ n ) = ∑ i = 1 n r i r i + 1 s i n θ 2

于是对于两个函数求偏导,就有了

riri+1cosθ2=λ r i r i + 1 c o s θ 2 = λ

i=1nθi=2π ∑ i = 1 n θ i = 2 π

然后发现lambda越大,theta越小,满足单调性
然后就可以用拉格朗日乘数解决

代码

#include 
#define pb push_back
using namespace std;
const int N = 10;
const int inf = 1e6;
inline int read()
{
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

double r[N]; int n; double pi = acos(-1);

int a[N]; bool v[N]; double maxx=0.0;

bool check(double lambda,int k)
{
  double ans = 0.0;
  for(int i=1;idouble theta = 2*lambda / r[a[i]] / r[(i+1) >= k ? a[1] : a[(i+1)]];
    if(theta > 1) return 1;
    if(theta < -1) return 0;
    ans += acos(theta);
  }return ans<=(2*pi);
}

void dfs(int k)
{
  if(k>=4)
  {
   // for(int i=1;i<=n;i++) printf("%d ",a[i]); printf("\n");
    double lambda = 0;
    double L = -inf; double R = inf;
    while(R-L>=1e-3)
    {
      double mid = (L+R)/2.0;
      if(check(mid,k)) R=mid,lambda=mid;
      else L=mid;
    }
    //cout<
    double ans=0.0;
    for(int i=1;idouble theta = acos(lambda * 2 / r[a[i]] / r[(i+1) >= k ? a[1] : a[(i+1)]]);
      ans += r[a[i]] * r[(i+1) >= k ? a[1] : a[(i+1)]] / 2 * sin(theta);
    }
    maxx = max(maxx , ans);
    // for(int i=1;i
    // 
    if(k==n+1) return ;
  }
  for(int i=1;i<=n;i++) if(!v[i]){a[k] = i; v[i] = 1; dfs(k+1); v[i] = 0;}
}

int main()
{
  freopen("yja.in","r",stdin);
  freopen("yja.out","w",stdout);

  // printf("%.6f\n",acos(1.1));
  n=read(); for(int i=1;i<=n;i++) r[i] = read();

  memset(v,0,sizeof(v)); dfs(1);

  return printf("%.10f\n",(double)maxx),0;
}

day4

Function

题意

给定m元的不等式

1in,xit ∀ 1 ≤ i ≤ n , x i ≤ t

i=1mxis ∑ i = 1 m x i ≤ s

nm n ≤ m

给定S,t,n,m,求出x的正整数解的个数mod 10^9+7

对于30%的数据

S,m106 S , m ≤ 10 6

对于100%的数据
ntS1018,nm109,t109,mn1000 n t ≤ S ≤ 10 18 , n ≤ m ≤ 10 9 , t ≤ 10 9 , m − n ≤ 1000

分析

这里只给30分的做法
我们可以考虑容斥原理,枚举有i个强制选t,枚举答案,于是有

i=0nk=0s(kti1m1)(1)i(ni)=i=0n(stim)(1)i(ni) ∑ i = 0 n ∑ k = 0 s ( k − t i − 1 m − 1 ) ( − 1 ) i ( n i ) = ∑ i = 0 n ( s − t i m ) ( − 1 ) i ( n i )

100分的做法太难了我不会啊谁来教教我


Subset

题意

给你1到n的排列ai,bi,ci
三元组(x,y,z)合法,当且仅当存在一个下标集合S是[n]的子集满足

(x,y,z)=(maxisai,maxjsbj,maxksck) ( x , y , z ) = ( m a x i ∈ s a i , m a x j ∈ s b j , m a x k ∈ s c k )

1<=n<=200000

分析

我们只要考虑,对于这样的一个三元组,只要选出1,2,3列,就可以对应一个三元组

选出1列的直接就是有n个可能的三元组
选出2列的就要减去a,b,c都在同一列的,就可以用一个CDQ三维数点来维护
选出3列的直接算不好算,我们考虑间接算
记A集合是选出3列中,其中有一列a,b,c都是最大的方案数,也是可以通过之前的CDQ处理
记B集合是选出3列中,其中有一列在a,b,c中任选两个是最大的方案数
这个还不好求,因为有a,b,c,我们试图也间接求这个东西

假设最大的是a,b,我们先不看c,找到三列中a,b都是最大的,c的关系我们忽略,同样的,忽略a,b算一次,我们记这个总的方案数是X

再确定c的关系,除去A集合的剩下的都是B集合的

3|A|+|B|=|X| 3 | A | + | B | = | X |

于是求出了A和B,这道题就结束了

代码

#include 
#define ll long long
using namespace std;
const ll N = 100010;
inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node
{
  ll a,b,c,d;
}q[N]; ll cnt[N]; ll n;
bool cmpa(const node &x,const node &y){return x.a<y.a;}
bool cmpb(const node &x,const node &y){return x.b<y.b;}
bool cmpc(const node &x,const node &y){return x.c<y.c;}

ll tr[N];
ll low_bit(ll x){return x&(-x);}
void add(ll x,ll c){while(x<=n){tr[x]+=c; x+=low_bit(x);}}
ll qry(ll x){ll s=0; while(x>=1){s+=tr[x]; x-=low_bit(x);} return s;}

void CDQ(ll L,ll R)
{
  if(L==R){return ;}
  ll mid=(L+R)>>1;
  CDQ(L,mid); CDQ(mid+1,R);
  sort(q+L,q+mid+1,cmpb); sort(q+mid+1,q+R+1,cmpb);
  ll j = L;
  for(ll i=mid+1;i<=R;i++)
  {
    while(q[j].b<=q[i].b && j<=mid){add(q[j].c,1); j++;}
    cnt[q[i].d] += qry(q[i].c);
  }
  while(j>L){j--; add(q[j].c,-1);}
}

int main()
{
  freopen("subset.in","r",stdin);
  freopen("subset.out","w",stdout);

  n = read();
  for(ll i=1;i<=n;i++) q[i].a=read(),q[i].d=i;
  for(ll i=1;i<=n;i++) q[i].b=read();
  for(ll i=1;i<=n;i++) q[i].c=read();
  sort(q+1,q+n+1,cmpa);
  CDQ(1,n);
  ll ans = n;
  ans += n*(n-1)/2; for(ll i=1;i<=n;i++) ans-=cnt[i];
  ans += n*(n-1)*(n-2)/6;
  ll A = 0; for(ll i=1;i<=n;i++) if(cnt[i]>=2) A+=cnt[i] * (cnt[i]-1) / 2;
  ll X = 0;

  sort(q+1,q+n+1,cmpa); memset(tr,0,sizeof(tr));
  for(ll i=1;i<=n;i++)
  {
    ll s = qry(q[i].b);
    X+=s*(s-1)/2; add(q[i].b,1);
  }

  sort(q+1,q+n+1,cmpa); memset(tr,0,sizeof(tr));
  for(ll i=1;i<=n;i++)
  {
    ll s = qry(q[i].c);
    X+=s*(s-1)/2; add(q[i].c,1);
  }

  sort(q+1,q+n+1,cmpb); memset(tr,0,sizeof(tr));
  for(ll i=1;i<=n;i++)
  {
    ll s = qry(q[i].c);
    X+=s*(s-1)/2; add(q[i].c,1);
  }

  ll B = X - 3*A;
  ans -= A + B;

  return printf("%lld\n",ans),0;
}

Tree

题意

给你一颗有n 个点的树,其中1 号点为根节点,每个点都有一个权值val[i]
你可以从树中选择一些点,注意如果i 与j 都被选中且j 在i 的子树内,那么必须满足val[i]>val[j]
请你求出最多能同时选出多少个点

分析

这是一道树上的LIS问题
一开始状态的定义是dp[i][0]表示这个结点选不选,发现这样很难转移与优化,我们考虑换个状态

因为子树内选不选和大小有关系,考虑状态dp[i][j]表示i结点子树下面最大值为j
这样的话你维护上去就维护一个当前点的值val[i],你找到一个val[i] - 1的前缀最大值,然后对于后面val[i] - n,你都把这个最大值+1用永久化标记放上去max一下,而子树互不干扰直接+上去

就是要线段树合并还有标记永久化,但是我不会
这里给出一个美妙的做法
在树上的LIS和普通的LIS,也是用一个数组维护,然后二分找到一个>=val[i]的替换掉
在树上也是同理,每个点用一个set维护一些权值,假设一个权值为x
那么如果这个权值x在set中排名为k,那么代表选x有k个贡献

于是像第一种做法一样,子树互不干扰的+操作相当于启发式合并set,然后看看当前结点的权值在set中找到最大的小于等于的替换掉,替换掉不改变后面的排名,这样就可以了。

代码

#include 
using namespace std;
const int N = 200010;
inline int read()
{
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node{int x,y,next;}edge[N<<1]; int len,first[N];
void ins(int x,int y){len++; edge[len].x=x; edge[len].y=y; edge[len].next=first[x]; first[x]=len;}

int n,val[N]; multiset<int>s[N];
void merge(int x,int y)
{
  if(s[x].size() < s[y].size()) swap(s[x],s[y]);
  multiset<int> :: iterator it;
  for(it = s[y].begin();it != s[y].end();++it) s[x].insert(*it);
  s[y].clear();
}
void dfs(int x)
{
  for(int k=first[x];k!=-1;k=edge[k].next)
  {
    int y=edge[k].y;
    dfs(y); merge(x,y);
  }
  multiset<int> :: iterator it = s[x].lower_bound(val[x]);
  if(it != s[x].end()) s[x].erase(it);
  s[x].insert(val[x]);
}

int main()
{
  freopen("tree.in","r",stdin);
  freopen("tree.out","w",stdout);
  n=read(); len=0; memset(first,-1,sizeof(first));
  for(int i=1;i<=n;i++){val[i] = read(); int f=read(); if(f) ins(f,i);}
  dfs(1);
  return printf("%d\n",s[1].size()),0;
}

day5

Max(CSA Expected Max)

题意

给你一个长度为 n n 的序列A,从1开始标记,全为0,进行 m m 次操作,对于第i次操作,你可以选一个二元组 (j,k) ( j , k ) j[1,n],k[0,c] j ∈ [ 1 , n ] , k ∈ [ 0 , c ] ,并让 Aj=Aj+k A j = A j + k ,其中选定二元组 (j,k) ( j , k ) 的概率是 Pi,j,k P i , j , k

询问m次操作后序列最大值的期望 mod109+7 m o d 10 9 + 7

分析

我们不能从操作那里入手枚举操作来dp,这样好像没办法记录到之前的序列A的状况,不妨我们枚举序列A中的每一位

f[i][mask][k] f [ i ] [ m a s k ] [ k ] 表示序列的前i位,已经用了mask个状态的操作,然后最大值为k的概率,我们如果要求期望,可以直接乘上k即可

这样考虑转移需要什么,我们需要知道mask状态加到序列第i位的概率
于是就有了另外一个状态 g[i][mask][k] g [ i ] [ m a s k ] [ k ] 表示第i位用mask的操作最大值为k

考虑转移的时间,加起来是 O(n2m(mc)2+n3m(mc)2) O ( n 2 m ( m c ) 2 + n 3 m ( m c ) 2 )

代码

#include 
#define ll long long
#define bin(i) (1<<(i))
using namespace std;

const ll N = 41;
const ll Mod = 1e9 + 7;

inline ll read()
{
  ll p=0; ll f=1; char ch = getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

ll p[41][11][4];

ll g[41][1<<11][31]; // i mask sum
ll f[41][1<<11][31]; // i mask sum

ll s[2][31],cur=1;

ll num[1<<11];

int main()
{
  freopen("max.in","r",stdin);
  freopen("max.out","w",stdout);

  ll n = read(); ll m = read(); ll c = read();
  for(ll j=0;j<m;j++) for(ll i=1;i<=n;i++) for(ll k=0;k<=c;k++) p[i][j][k] = read();


  for(ll i=1;i<=n;i++)
  {
    for(ll j=0;jm);j++)
    {
      cur=1; for(ll k=0;k<=c*m;k++) s[cur][k] = 0,s[cur^1][k] = 0; s[cur][0] = 1;
      for(ll k=0;k<m;k++) if(j&bin(k))
      {
        for(ll now=0;now<=c*m;now++)
          for(ll nxc=0;nxc<=c;nxc++) s[cur^1][now+nxc] = (s[cur^1][now+nxc] + s[cur][now] * p[i][k][nxc] % Mod ) % Mod;
        for(ll now=0;now<=c*m;now++) s[cur][now] = 0; cur ^= 1;
      }
      for(ll k=0;k<=c*m;k++) g[i][j][k] = s[cur][k];
    }
  }

  for(ll i=0;im);i++) num[i] = __builtin_popcount(i);

  f[0][0][0] = 1;
  for(ll i=1;i<=n;i++)
  {
    for(ll j=0;jm);j++) for(ll psta = j; ;psta = (psta-1) & j)
    {
      ll x = num[j]; ll y = num[psta];
      for(ll k=0;k<=c*x;k++) for(ll lst=0;lst<=c*y;lst++)
        f[i][j][max(k,lst)] =(f[i][j][max(k,lst)] +  (f[i-1][psta ^ j][k] * g[i][psta][lst] % Mod) ) % Mod;
      if(psta == 0) break ;
    }
  }

  ll ans = 0 ; for(ll k=0;k<=c*m;k++) ans = (ans + k * f[n][bin(m)-1][k]) % Mod;

  return printf("%lld\n",ans),0;
}

Paint(AtCoder Regular Contest 063 F)

题意

小C很喜欢二维染色问题,这天他拿来了一个w  h的二维平面,初始时均为
白色.然后他在上面设置了n个关键点(Xi,Yi),对于每个关键点他会选择进行下
列操作的一个:
将x > Xi的部分染成黑色.
将x < Xi的部分染成黑色.
将y > Yi的部分染成黑色.
将y < Yi的部分染成黑色.
图示参见样例解释
他本来是想让你支持单点修改以及可持久化然后把空间限制开成M的,但鉴
于这只是第二题,现在他只想最大化所有操作结束之后白色部分的周长(不难发
现白色部分一定是个矩形).特别地,如果没有白色部分,设其周长为0 (N<=2*10^5)

分析

这道题很好啊
首先一个结论,答案的下限是2*max(w,h)+2 , 肯定经过w/2或者h/2的中轴线,这个可以用反证法或者随便就可以证出来,这样有什么用呢,我们就要利用一下性质

对于中轴线,我们可以维护那些点到中轴线的距离,我们现在假设有上端点和下端点分别是 yr y r , yl y l 那么我们的左端点和右端点要满足

xl=min{xi|yi(yl,yr),xiw2} x l = m i n { x i | y i ∈ ( y l , y r ) , x i ≤ w 2 }

xr=min{xi|yi(yl,yr),xi>w2} x r = m i n { x i | y i ∈ ( y l , y r ) , x i > w 2 }

然后这个东西我们就相当于对中轴线的两边维护一个单调栈,表示可以取的下边界,显然我们要维护的是,越下面的下边界越短,然后对纵坐标离散化之后用线段树维护一个值(下边界的长 - 当前纵坐标)因为你的答案要减去纵坐标

对于每个点,你先找到以这个点为上边界的,然后再把它分别加入两边单调栈,具体可以看代码

然后反过来再做一遍就好了

代码

#include 

#define ll long long
#define pii pair

using namespace std;

const ll N = 300010;
const ll inf = 2e9;

inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node
{
  ll x,y;
  node(){}
  node(ll _x,ll _y){x=_x; y=_y;}
}q[N],a[N],b[N]; ll sta,stb;

ll w,h,n;

ll rt,lc[N<<2],rc[N<<2],c[N<<2],lazy[N<<2],tot = 0;
void link(ll &u,ll L,ll R,ll k,ll cc)
{
  if(!u) u=++tot,lazy[u] = 0;
  if(L==R){c[u] = cc ; return ;}
  ll mid = (L+R)>>1;
  if(k<=mid) link(lc[u],L,mid,k,cc);
  else link(rc[u],mid+1,R,k,cc);
  c[u] = max(c[lc[u]] , c[rc[u]]);
}

ll ans = 0;

void push_down(ll u)
{
  if(lazy[u])
  {
    c[lc[u]]+=lazy[u];
    c[rc[u]]+=lazy[u];
    lazy[lc[u]] += lazy[u]; lazy[rc[u]] += lazy[u];
    lazy[u] = 0;
  }
}

void chg(ll u,ll L,ll R,ll l,ll r,ll cc)
{
  if(l>r) return ;
  if(L==l && R==r)
  {
    c[u] += cc; lazy[u]+=cc;
    return ;
  }
  ll mid=(L+R)>>1;
  push_down(u);
  if(r<=mid) chg(lc[u],L,mid,l,r,cc);
  else if(l>mid) chg(rc[u],mid+1,R,l,r,cc);
  else
  {
    chg(lc[u],L,mid,l,mid,cc);
    chg(rc[u],mid+1,R,mid+1,r,cc);
  }
  c[u] = max(c[lc[u]] , c[rc[u]]);
}

bool cmpy(const node &x,const node &y){return x.y < y.y;}

void solve()
{
  sort(q+1,q+n+1,cmpy);
  rt=tot=0; memset(c,0,sizeof(c)); memset(lazy,0,sizeof(lazy));
  memset(lc,0,sizeof(lc)); memset(rc,0,sizeof(rc));
  for(ll i=1;i<=n;i++) link(rt,1,n,i,-inf);
  sta = stb = 0; ll mid = w/2;
  for(ll i=1;iif(q[i].x<=mid)
    {
      ll nx = i-1;
      while(sta && a[sta].x <= q[i].x)
      {
        chg(rt,1,n,a[sta].y,nx,a[sta].x - q[i].x);
        nx = a[sta].y-1; sta--;
      }
      a[++sta] = node(q[i].x , nx+1);
    }
    else
    {
      ll nx = i-1;
      while(stb && b[stb].x >= q[i].x)
      {
        chg(rt,1,n,b[stb].y,nx,q[i].x - b[stb].x);
        nx = b[stb].y-1; stb--;
      }
      b[++stb] = node(q[i].x , nx+1);
    }
    a[++sta] = node(0,i);
    b[++stb] = node(w,i);
    chg(rt,1,n,i,i,inf - q[i].y + w);
    ans = max(ans , c[rt] + q[i+1].y);
  }
}

int main()
{
  w = read(); h = read(); n = read();
  for(ll i=1;i<=n;i++) q[i].x = read() ,  q[i].y = read();
  q[++n] = node(0,0); q[++n] = node(w,h);

  solve();
  swap(w,h); for(ll i=1;i<=n;i++) swap(q[i].x,q[i].y);
  solve();

  return printf("%lld\n",ans<<1),0;
}

day7

A

题意

给你一个数列N,询问Q,要你支持区间询问最大值,区间异或,区间与
N<=200000

分析

这道题直接搞就好了?
理由如下:
首先很快想到拆开每一个二进制位来处理
对于二进制相同的位,我们可以打一个标记,因为无论是变成0和变成1 ,都可以一起变
这样的话,一开始的标记最多有20N个
这样考虑你一次修改,如果一个大区间打了标记,而你要进入一个小区间里面,增加的标记的数量是log个的。
但是如果你要修改的是一个大区间,而这个大区间没有被打标记,那么肯定要搜到小区间里面分别修改,而每次搜到一个小区间的结点,减少标记是1

所以这样就可以证明每次你搜到的结点个数是 O(20NlogN) O ( 20 N l o g N )

维护一个区间或,区间与,或和与标记,然后下传的时候,先下传与标记,再下传或标记,避免或标记在后的受到与标记的影响

代码

#include 
using namespace std;

const int N = 200010;
const int mx = (1<<20)-1;

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

int a[N]; int rt,tot,lc[N<<2],rc[N<<2],land[N<<2],lor[N<<2],sand[N<<2],sor[N<<2],c[N<<2];

void update(int u)
{
  sand[u] = sand[lc[u]] & sand[rc[u]];
  sor[u] = sor[lc[u]] | sor[rc[u]];
  c[u] = max(c[lc[u]] , c[rc[u]]);
}

void push_down(int u)
{
  if(land[u]!=mx)
  {
    land[lc[u]] &= land[u]; land[rc[u]] &= land[u];
    lor[lc[u]] &= land[u]; lor[rc[u]] &= land[u];
    sand[lc[u]] &= land[u]; sand[rc[u]] &= land[u];
    sor[lc[u]] &= land[u]; sor[rc[u]] &= land[u];
    c[lc[u]] &= land[u]; c[rc[u]] &= land[u];
    land[u] = mx;
  }
  if(lor[u])
  {
    lor[lc[u]] |= lor[u]; lor[rc[u]] |= lor[u];
    sand[lc[u]] |= lor[u]; sand[rc[u]] |= lor[u];
    sor[lc[u]] |= lor[u]; sor[rc[u]] |= lor[u];
    c[lc[u]] |= lor[u]; c[rc[u]] |= lor[u];
    lor[u] = 0;
  }
}

int build(int L,int R)
{
  tot++; int now = tot; land[now] = mx,lor[now] = 0;
  if(Lint mid=(L+R)>>1;
    lc[now] = build(L,mid); rc[now] = build(mid+1,R);
    update(now);
  }
  else c[now] = sand[now] = sor[now] = a[L];
  return now;
}

int qry(int u,int L,int R,int l,int r)
{
  if(L == l && R == r) return c[u];
  int mid=(L+R)>>1;
  push_down(u);
  if(r<=mid) return qry(lc[u],L,mid,l,r);
  else if(l>mid) return qry(rc[u],mid+1,R,l,r);
  else return max(qry(lc[u],L,mid,l,mid) , qry(rc[u],mid+1,R,mid+1,r)); 
}

void cor(int u,int L,int R,int l,int r,int x)
{
  if(L == l && R == r)
  {
    int p = (sand[u] & x) | ((sor[u] ^ mx) & x);
    if((p&x) == x)
    {
      sor[u] |= x; sand[u] |= x;
      lor[u] |= x; c[u] |= x;
      //printf("SB.. %d %d\n",L,R);
      return ;
    }
  }
  int mid=(L+R)>>1;
  push_down(u);
  if(r<=mid) cor(lc[u],L,mid,l,r,x);
  else if(l>mid) cor(rc[u],mid+1,R,l,r,x);
  else
  {
    cor(lc[u],L,mid,l,mid,x);
    cor(rc[u],mid+1,R,mid+1,r,x);
  }update(u);
}

void cand(int u,int L,int R,int l,int r,int x)
{
  if(L == l && R == r)
  {
    int p = ((sor[u] ^ mx) & (x ^ mx)) | ((x^mx) & sand[u]); //鐩稿悓鏄?
    if((p | x) == mx)
    {
      sor[u] &= x; sand[u] &= x;
      land[u] &= x; lor[u] &= x; c[u] &= x;
      //printf("SB... %d %d\n",L,R);
      return ;
    }
  }
  int mid=(L+R)>>1;
  push_down(u);
  if(r<=mid) cand(lc[u],L,mid,l,r,x);
  else if(l>mid) cand(rc[u],mid+1,R,l,r,x);
  else
  {
    cand(lc[u],L,mid,l,mid,x);
    cand(rc[u],mid+1,R,mid+1,r,x);
  }update(u);
}

int main()
{
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);

  int n = read(); int q = read(); 

  for(int i=1;i<=n;i++) a[i] = read();
  rt=tot=0; rt=build(1,n);

  while(q--)
  {
    int op = read();
    if(op==3)
    {
      int l=read(); int r=read();
      printf("%d\n",qry(rt,1,n,l,r));
    }
    else
    {
      int l = read(); int r = read(); int x = read();
      if(op==1) cand(rt,1,n,l,r,x);
      else cor(rt,1,n,l,r,x);
    }
  }

  return 0;
}

C

题意

A和B分别操作一颗树上的结点,定义KA为A的连通块的个数,KB为B的连通块的个数,A想让A-B的连通块尽量的多,B想让A-B的连通块尽量的少

分析

首先连通块的数量 = 点数 - 边数
考虑只有两个点都是同一个颜色的,连通块个数才会-1

因为答案是

VaVbEa+Eb V a − V b − E a + E b

那么对于边的两个端点分别+1,然后A取的话就-去这个点的权值,B取的话+这个点的权值

那么一条边两个端点是不同的人取的话,那么贡献就抵消了,否则A取的话-2,B取的话+2,B想让上式最小,A想让上式最大,都是取权值尽量小的
现在我们是求出了

2(EbEa) 2 ( E b − E a )

于是对权值排个序,然后除于2就好

代码

代码没存不见了233333。。。。
这个20行的事嘛


day8

A

题意

给每个点一个power值,对其他的点的影响为 max(0,poweridis(posij)) m a x ( 0 , p o w e r i − d i s ( p o s i − j ) ) 然后求出每个点的贡献
N<=200000;M<=500000

分析

这道题用点分治做,我们不能一个个点对去求
有一个想法就是对于一个子树x和一个子树y,考虑x的子树影响到y
肯定有 powerxdxdy p o w e r x − d x ≥ d y
然后可以用树状数组来维护 powerxdx p o w e r x − d x ,对于 dy d y 只需要找后缀和就好了
对于power比较大的点就直接统计
这样做法的时间复杂度是 O(Nlog2N) O ( N l o g 2 N )
我们要更优的做法,只需要用一个桶之类的维护 powerxdx p o w e r x − d x ,然后再bfs找y的子树就好了
深度是递增的,然后维护一个 powerxdx p o w e r x − d x 的和然后减掉即可
注意还需要计算重心连通块内的贡献

代码

#include 
#define pb push_back
#define MP make_pair
using namespace std;
typedef long long ll;
typedef pair pii;
const ll N = 200010;
inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}
vector G[N]; ll n,m; vectorh[N]; ll ans[N];
bool bo[N]; ll p=0,mx,siz[N];

void dfs1(ll x,ll f)
{
  siz[x] = 1;
  for(ll i=0;iif(y==f || bo[y]) continue;
    dfs1(y,x); siz[x] += siz[y];
  }
}

void find_root(ll x,ll f,ll tot)
{
  ll s = tot - siz[x];
  for(ll i=0;iif(bo[y] || y==f) continue;
    find_root(y,x,tot); s = max( s , siz[y]);
  }
  if(s < mx){mx = s; p = x;} 
}

queue >q; ll cnt[N] , sum[N];  ll sp,gs = 0; // 桶内和桶外的和,桶内和桶外的个数

bool vis[N];
void bfs(ll ss,ll dep)
{
  while(!q.empty()) q.pop();
  vis[ss] = 1; q.push(MP(ss,dep)); ll now = 0; ll s = sp; ll c = gs;
  while(!q.empty())
  {
    pii x = q.front();
    while(now < x.second){c-=cnt[now]; s-=sum[now]; now ++;}
    ans[x.first] += s - c * x.second;
    for(ll i=0;iif(bo[y] || vis[y]) continue;
      vis[y] = 1; q.push(MP(y,x.second+1));
    }q.pop();
  }
}

void calc(ll x,ll f,ll dep,ll op)
{
  vis[x] = 0;
  for(ll i=0;iif(y<=0) continue; sp += y * op; gs += op;
    if(y <= n) sum[y] += y * op,cnt[y]+=op;
  }
  for(ll i=0;iif(y==f || bo[y]) continue;
    calc(y,x,dep+1,op);
  }
}

void dfs(ll x,ll f)
{
  dfs1(x,0); mx = INT_MAX; find_root(x,0,siz[x]); x=p; 

  gs = sp = 0; vis[x] = 1;
  for(ll i=0;iif(bo[y] || y==f) continue;
    bfs(y,1);
    calc(y,x,1,1);
  }

  vis[x] = 0;
  for(ll i=0;iif(bo[y] || y==f) continue;
    calc(y,x,1,-1);
  }

  for(ll i=0;i1;
    if(y<=n) sum[y] += y,cnt[y] ++;
  }

  vis[x] = 1;
  for(ll i=G[x].size()-1;i>=0;i--)
  {
    ll y = G[x][i];
    if(bo[y] || y==f) continue;
    bfs(y,1);
    calc(y,x,1,1);
  }ans[x] += sp;

  vis[x] = 0;
  for(ll i=G[x].size()-1;i>=0;i--)
  {
    ll y = G[x][i];
    if(bo[y] || y==f) continue;
    calc(y,x,1,-1);
  }

  for(ll i=0;i1;
    if(y<=n) sum[y] -= y,cnt[y] --;
  }

  bo[x] = 1;
  for(ll i=0;iif(bo[y] || y==f) continue;
    dfs(y,x);
  }
}
int main()
{
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
  n = read(); m = read(); for(ll i=1;i1); G[i+1].pb(x);}
  while(m--)
  {
    ll x = read(); ll y = read();
    h[x].pb(y);
  }

  dfs(1,0);
  for(ll i=1;i<=n;i++) printf("%lld\n",ans[i]);
  return 0;
}

B

题意

求有多少N 个的竞赛图包含至少一个长度为K 的简单环, 输出答案模
10^9 + 7 的结果.
竞赛图: 任意两个点之间都有一条有向边的图.
简单环: 不经过重复节点的回路.

分析

首先对于一个竞赛图,我们缩完联通分量之后,剩下的肯定是一个DAG,而且是一条链的形式,然后我们就可以用 dp[i][0/1] d p [ i ] [ 0 / 1 ] 来解决这个问题,除此之外,我们还要预处理出大小为k的强联通的方案数,考虑到这是一个竞赛图,就有
g[n]=2(n2)+i=1n1(ni)g[i]2(ni2) g [ n ] = 2 ( n 2 ) + ∑ i = 1 n − 1 ( n i ) g [ i ] 2 ( n − i 2 )

代码

#include 
using namespace std;
typedef long long ll;
const ll Mod = 1e9+7;
const ll N = 5010;
inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}
ll g[N]; ll bin[N]; ll f[N][2]; int C[N][N];

void upd(ll &x,ll y){x=(x+y) % Mod;}

ll qpow(ll x,ll k,ll mo)
{
  ll s=1; while(k){if(k&1) s=s*x%mo; x=x*x%mo; k>>=1;} return s;
}

int main()
{
  freopen("b.in","r",stdin);
  freopen("b.out","w",stdout);

  ll n = read(); ll k = read();
  C[0][0] = 1;for(ll i=1;i<=n;i++)
  {
    C[i][0] = 1;
    for(ll j=1;j<=i;j++) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % Mod;
  }

  for(ll i=1;i<=n;i++) bin[i] = qpow(2,i*(i-1)/2,Mod);

  for(ll i=1;i<=n;i++)
  {
    g[i] = bin[i];
    for(ll j=1;jmemset(f,0,sizeof(f)); f[0][0] = 1;
  for(ll i=1;i<=n;i++)
  {
    for(ll j=1;j<=i;j++)
    {
      if(j>=k)
        upd(f[i][1] , ((f[i-j][0] + f[i-j][1]) % Mod  * g[j] % Mod * C[n-(i-j)][j] % Mod) );
      else
        upd(f[i][1] , (f[i-j][1] * g[j] % Mod * C[n-(i-j)][j] % Mod)),
        upd(f[i][0] , (f[i-j][0] * g[j] % Mod * C[n-(i-j)][j] % Mod));
    }
  }

  return printf("%lld\n",f[n][1]),0;
}

你可能感兴趣的:(雅礼集训)