2017国庆 雅礼集训 题解合集

D1

D1 T1:Clique:
我做的题太少啦,这都没看出来。首先,这个式子是 c[i]c[j]>=dis(i,j) ,即在数轴上这样的圆,如果没交点,那么就有边,所以就是最长区间不覆盖。

#include<cstdio>
#include<algorithm>
using namespace std;
const int size = 200005;
struct seg{
    long long l, r;
    inline bool operator <(const seg &fu)const
    {if (fu.r==r) return l < fu.l; 
    return r < fu.r; }
}a[size];

long long n;
int main()
{
    scanf("%lld",&n);
    for (long long i=1;i<=n;i++)
    {
        long long tv,tw;
        scanf("%lld%lld",&tv,&tw);
        a[i].l = tv-tw;a[i].r = tv + tw;
    }
    sort(a+1,a+1+n);
    long long r = a[1].r;
    long long ans = 1;
    for (long long i = 2 ; i <= n ; i++)
    {
        if (a[i].l >= r)  
        { 
        ans++; 
        r = a[i].r;
        }    
    }    
    printf("%lld",ans);

    return 0;
}

D1 T2:Mod
好题啊!!要记下来。
套路题。首先,如果一个区间内最大的数都没有模数大,就不用模啦,因为这是无效的膜。又每次取模后,都只最多剩下以前的一半,所以在这个数不变的情况下,这个数最多有效的被模 logn 次,这个可以暴力修改。那么每个数被修改 logn 次,一次改是 logn 的,总的就是 nlognlogn

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll; 
const int size = 100005;
struct NODE{
    ll maxn,cnt;
}t[4*size];
ll n,m;

void update1(ll l,ll r,ll qx,ll y,ll node)
{
    if (l==r)
    {
        t[node].maxn = y;
        t[node].cnt = y; 
        //printf("fuck %d %d\n",y,t[l].cnt);
        return ;
    }
    ll mid=(l+r)>>1;
    if (qx <= mid) update1(l,mid,qx,y,node*2);
    if (qx >= mid+1) update1(mid+1,r,qx,y,node*2 + 1);    
    t[node].maxn = max( t[node * 2].maxn , t[node*2+1].maxn ) ;
    t[node].cnt = t[node*2].cnt + t[node*2+1].cnt ;    
    //printf("node %d l %d r %d cnt %d maxn %d mid %d\n",node,l,r,t[node].cnt,t[node].maxn,mid);
}

void build(ll node,ll l,ll r)
{
    if (l==r)
    {
        t[node].cnt=0;
        t[node].maxn=0;
        return ;
    }
    ll mid = (l+r)>>1;
    build(node*2,l,mid);
    build(node*2+1,mid+1,r);
}

void update2(ll ql,ll qr,ll l,ll r,ll x,ll node)
{
    if (r < ql || l > qr) return;
    if (t[node].maxn < x) return;
    if (l==r)
    {
        t[node].cnt%=x;
        t[node].maxn%=x;
        return;
    }
    ll mid = (l+r)>>1;
    update2(ql,qr,l,mid,x,2*node);
    update2(ql,qr,mid+1,r,x,2*node+1);
    t[node].maxn = max(t[2*node].maxn , t[2*node+1].maxn);
    t[node].cnt = t[2*node].cnt + t[node*2+1].cnt;    
}
ll query(ll l,ll r,ll ql,ll qr,ll node)
{
    if (r < ql || l > qr) return 0;
    ll ans = 0;
    if (ql <= l && r <= qr) return t[node].cnt; 
    ll mid = l+r>>1;
    ans += query(l,mid,ql,qr,2*node) + query(mid+1,r,ql,qr,2*node+1);
    return ans;
}
void dfs(ll l,ll r,ll node)
{
    printf("l is %d r is %d node is %d cnt is %d \n",l,r,node,t[node].cnt);
    if (l==r) return ;
    ll mid=l+r>>1;
    dfs(l,mid,node*2);
    dfs(mid+1,r,node*2+1);
}
int main()
{
    //freopen("mod.in","r",stdin);
    //freopen("modfuck.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    ll dod;
    build(1,1,n);
    for (ll i=1;i<=n;i++)
    {
        ll tmp;scanf("%lld",&tmp);
        update1(1,n,i,tmp,1);
    }

    for (ll i=1;i<=m;i++)
    {
        scanf("%lld",&dod);
        if (dod==1)
        {
        ll l,r;scanf("%lld%lld",&l,&r);
        ll ans = query(1,n,l,r,1);
        printf("%lld\n",ans);    
        }else
        if (dod==2)
        {
            ll l,r,x;
            scanf("%lld%lld%lld",&l,&r,&x);
            update2(l,r,1,n,x,1);
            //dfs(1,n,1);

        }else
        if (dod==3)
        {
        ll k,y;
        scanf("%lld%lld",&k,&y);
        update1(1,n,k,y,1);
        }
    }
    return 0;
}

D1 T3:Number
就是你会发现,随着n的变大,那段区间在二进制中有 k 个一的数不会减少。因为从k k+1 时,有 2k 在二进制中与 k 1的个数相同,所以不会减少。那么就可以数位dp一下。令 S(i)=i+1,2,,2i f(i,k) 表示 S(i) 中二进制下恰好有 k 1的数的个数 i>0,k0 。设为中有几个数 f(i,k)=Σmin(k,p)x=1Ckx+1ax+1Ckx+1ax ,其中 p 表示i在二进制下1的个数, ax 表示 i在二进制下第x高的1所在位代表的2的幂次。特判掉 k1 ,那么所有满足条件的 n都在 21018 以内。 用二分或逐位贪心求出最小和大的 n 即可。

#include <iostream>
#include <cstdio>
#define MAX_DIG 63
using namespace std;
typedef unsigned long long lnt;
int k;    lnt m, c[MAX_DIG+5][MAX_DIG+5], pow[MAX_DIG+5] = {1}, range = 2e18;
void init() {
    for (int i = 1; i <= MAX_DIG; i++)    pow[i] = pow[i-1]*2;
    for (int i = 0; i <= MAX_DIG+2; i++)    c[i][0] = 1, c[i][i] = 1;
    for (int i = 1; i <= MAX_DIG+2; i++)    for (int j = 1; j <= i; j++)    c[i][j] = c[i-1][j-1]+c[i-1][j];
}
lnt f(lnt tmp) {
    int p = 0;    lnt ret = 0;
    for (int i = 0; i <= MAX_DIG; i++)    if (tmp&pow[i])    p++;
    for (int x = 1, cur = MAX_DIG; x <= min(p, k); x++, cur--) {
        while (!(pow[cur]&tmp))    cur--;
        ret += c[cur+1][k-x+1]-c[cur][k-x+1];
    }
    return ret;
}
lnt fl(lnt l, lnt r) {
    lnt ret = -1;
    while (l <= r) {
        lnt mid = l+r>>1;
        if (f(mid) <= m)    ret = mid, l = mid+1;
        else    r = mid-1;
    }
    return ret;
}
lnt fu(lnt l, lnt r) {
    lnt ret = -1;
    while (l <= r) {
        lnt mid = l+r>>1;
        if (f(mid) >= m)    ret = mid, r = mid-1;
        else l = mid+1;
    }
    return ret;
}
int main() {
    int T; scanf("%d", &T), init();
    while (T--) {
        scanf("%llu%d", &m, &k);    if (m == 1 && k == 1) {printf("-1\n");    continue;}
        if (m == 0) {printf("1 %llu\n", pow[k-1]-1);    continue;}
        lnt lo =fu(1, range), hi = fl(1, range);
        printf("%llu %llu\n", lo, hi);
    }int sdf=0;
    for (int i=1;i<=10000000;i++) sdf+=0;
    return 0;
}

D2

D2 T1 :Mine
神奇的定义与转移。f(i,j) 表示当前填第 i 个字符为j的方案数,即j表示当前这个位置还缺几个雷,如果是2就表示这个位置本身就是雷。然后 dp 就好搞啦。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
typedef long long ll;
const int size = 1000005;
const ll mod = 1000000007;
char s[size];
int dp[size][10],len;

int main(){
    scanf("%s", s);
    len = strlen(s);
    if(len == 1){
        if(s[0] == '?')printf("2");
        if(s[0] == '1' || s[0] == '2')printf("0");
        return 0;
    }
    if(s[0] == '0')dp[0][0] = 1;
    if(s[0] == '1')dp[0][1] = 1;
    if(s[0] == '2'){printf("0");return 0;}
    if(s[0] == '*')dp[0][2] = 1;
    if(s[0] == '?')dp[0][0] = dp[0][1] = dp[0][2] = 1;
    for(int i = 1;i < len;i ++ )
    {
        if(s[i] == '0')  dp[i][0] = dp[i-1][0]; else 
        if(s[i] == '1')
        {
            if(i < len-1)dp[i][1] = dp[i-1][0];
            dp[i][0] = dp[i-1][2];
        }else 
        if(s[i] == '2')dp[i][1] = dp[i-1][2]; else 
        if(s[i] == '*')dp[i][2] = (dp[i-1][1] + dp[i-1][2]) % mod; else 
        if(s[i] == '?')
        {
            if(i < len-1) dp[i][1] = (dp[i-1][0] + dp[i-1][2]) % mod;
            dp[i][2] = (dp[i-1][1]  +  dp[i-1][2]) % mod;
            dp[i][0] = (dp[i-1][0]  +  dp[i-1][2]) % mod;
        }
    }
    ll ans = dp[len-1][0] + dp[len-1][2];
    printf("%lld\n", ans);
    return 0;
} 

D2 T2:Water
就是水如果能流出去,那么一定有一条路径使得其路径上的值都小于其高度。如果流不出去,那么所有路径上的最大值的最小值要大于其高度(因为一条路径上水只会被最高的挡住,而木桶原理,水只会在最低的被挡住的高度被留住)。所以问题就是从这个块走出矩形的所有路径上的最大值的最小值。相邻块连边,权值为两块的较大值,矩形边界的块向“矩形外”连边,权值为 max(,0) ,做最小生成树。(最短路也可以)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 1<<30
typedef long long ll;
const ll size = 500;
using namespace std;
struct EDGE{
    ll to,next,val;
    inline bool operator < (const EDGE &du )const
    {
        return val < du.val;
    }
}edge[100*size*size];ll cnt=0;
ll n,m,value;
ll mat[size][size] , dis[size*size], head[size*size];
bool in[size*size]; 

ll SPFA(ll node)
{
    for (ll i = 0 ; i <= n*m ;i++)dis[i] = inf;
    queue<ll> q;
    q.push(node);dis[node]= 0;
    memset(in,false,sizeof in);
    in[node] = true;
    while(!q.empty())
    {
        ll h = q.front(); 
        for (ll j=head[h] ; j ;j= edge[j].next)
        {
            if (dis[edge[j].to ] > max(dis[h],edge[j].val) )
            {
                dis[ edge[j].to ] = max(dis[h] , edge[j].val); 
                if (!in[edge[j].to])
                {
                q.push(edge[j].to);
                in[edge[j].to] = 1;
                }
            }
        }    
        q.pop();in[h] = false;   
    }
}
ll idx(ll i,ll j)
{
    return (i*m + j-m);
}
void adde(ll fr,ll to,ll val)
{
    edge[++cnt]= (EDGE){to,head[fr],val};
    head[fr] = cnt;
}
void addedge(ll fr,ll to,ll val)
{
    adde(fr,to,val);
    adde(to,fr,val);
}
int main()
{
    //freopen("water.in","r",stdin);
    //freopen("water.out","w",stdout);
    scanf("%lld%lld",&n,&m);
      for (ll i=1;i<=n;i++)
      for (ll j=1;j<=m;j++)
          scanf("%lld",&mat[i][j]);

      for (ll i=1;i<=n;i++)
      for (ll j=1;j<m;j++)
      {
          value = max(mat[i][j],mat[i][j+1]);
          addedge(idx(i,j),idx(i,j+1),value);
      }

      for (ll i=1;i<n;i++)
      for (ll j=1;j<=m;j++)
      {
          value = max(mat[i][j] , mat[i+1][j]);
          addedge(idx(i,j),idx(i+1,j),value);
      }
      for (ll i=2;i<=n-1;i++)
      {
      addedge(0 , idx(i,1) , max(mat[i][1] , (ll)0 ) );     
      addedge(0 , idx(i,m) , max(mat[i][m] , (ll)0 ) );       
      }
      for (ll j=1;j<=m;j++)   addedge(0,j,max((ll)0,mat[1][j]));
      for (ll j=1;j<=m;j++) addedge(0 , idx(n,j) , max((ll)0,mat[n][j]));           
      SPFA(0);
       for (ll i=1;i<=n;i++)
       {
               for (ll j=1;j<=m;j++)
               {
                   if (dis[idx(i,j)] <= mat[i][j]) printf("0 ");
                   else printf("%lld ",dis[idx(i,j)]-mat[i][j]);    
                   //printf("%lld ",dis[idx(i,j)]);
                }
            putchar('\n');
       }
       //for (ll i=1;i<=m*n;i++)
       //printf("%d ",dis[i]);
    return 0;  
}

D2 T3:Gcd:
乱反演一下,然后那个桶记因数出现次数。用 f(i) 表示gcd为i的数对个数, g(i) 表示gcd为i的倍数的数对个数。那么 f(i)=Σμ(d)g(d) ,我们只需要维护 g(1) g(max(xi)) 。记s(i)表示i的倍数的个数,那么 g(i)=s(i)(s(i)1)/2 ,我们只需要在加入/删除一个数时枚举它的因数修改s即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int size = 500005;
bool is_prime[size];
int prime[size],cnt,ud[size];
int s[size] , in[size]  ,a[size];
int n,m,maxn=size;
ll ans=0;

void doyoudo()
{
    memset(is_prime,true,sizeof is_prime);
    is_prime[1] = 1;ud[1]  = 1;
    for (int i=2;i<=maxn;i++)
    {
        if (is_prime[i]) 
        {
        prime[++cnt]  =  i;
        ud[i] = -1;
        }
        for (int j=1;j<=cnt;j++)
        {
            int tmp = i*prime[j];
            if (tmp>maxn) break;
            is_prime[tmp] = false;
            if (i %  prime[j]==0)
            {
                ud[tmp] = 0;
                break;
            }
            else
            ud[tmp]  =  ud[i]  * (-1);

        }
    }    
}

void add(int x)
{
    for (int i=1;i*i<=x;i++)
    {
        if (x%i) continue;
        ans += ud[i] * (s[i]++);
        if (i*i!=x)
        ans += ud[x/i] * (s[x/i]++);
    }
}

void delet(int x)
{
    for (int i=1;i*i<=x;i++)
    {
        if (x%i) continue;
        ans -= ud[i] * (--s[i]);
        if (i*i!=x)
        ans -= ud[x/i] * (--s[x/i]);
    }
}



int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    doyoudo();
    memset(in,0,sizeof in);
    while (m--)
    {
        int tmp;
        scanf("%d",&tmp);
        if (in[tmp]==1) delet(a[tmp]);
        else add(a[tmp]);
        in[tmp]^=1;
        printf("%lld\n",ans);
    }
    //for (int i=1;i<=maxn;i++)
    //printf("%d %d %d\n",i,is_prime[i],in[i]);
}

D3

D3 T1:string
就是对每种字母单独造棵线段树,然后合并。有点神。

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
int n,m,g[400010],p;
char s[100010];
struct str
{
    int a[26];
    inline void clear()
    {
        int i;
        for(i=0;i<26;i++)
          a[i]=0;
    }
    inline str operator+(str x)
    {
        str p;
        int i;
        for(i=0;i<26;i++)
          p.a[i]=a[i]+x.a[i];
        return p;
    }
}f[400010],x;
inline void down(int i,int k)
{
    if(g[i]!=-1)
      {
       g[i<<1]=g[i];
       f[i<<1].clear();
       f[i<<1].a[g[i]]=k;
       g[i<<1|1]=g[i];
       f[i<<1|1].clear();
       f[i<<1|1].a[g[i]]=k;
       g[i]=-1;
      }
}
inline void change(int i,int j,int k,int l,int r,int x)
{
    if(l<=j && k<=r)
      {
       f[i].clear();
       f[i].a[x]=k-j+1;
       g[i]=x;
      }
    else
      {
       down(i,k-j+1>>1);
       if(l<=(j+k>>1))
         change(i<<1,j,j+k>>1,l,r,x);
       if(r>(j+k>>1))
         change(i<<1|1,(j+k>>1)+1,k,l,r,x);
       f[i]=f[i<<1]+f[i<<1|1];
      }
}
inline str sum(int i,int j,int k,int l,int r)
{
    if(l<=j && k<=r)
      return f[i];
    else
      {
       down(i,k-j+1>>1);
       str p={};
       if(l<=(j+k>>1))
         p=p+sum(i<<1,j,j+k>>1,l,r);
       if(r>(j+k>>1))
         p=p+sum(i<<1|1,(j+k>>1)+1,k,l,r);
       return p;
      }
}
int main()
{
    //freopen("string.in","r",stdin);
    //freopen("string.out","w",stdout);
    int i,j,k;
    scanf("%d%d%s",&n,&m,&s);
    for(p=1;p<n;p<<=1);
    for(i=1;i<2*p;i++)
      g[i]=-1;   
    for(i=1;i<=n;i++)
      change(1,1,p,i,i,s[i-1]-'a');
    while(m--)
      {
       scanf("%d%d%d",&i,&j,&k);
       x=sum(1,1,p,i,j);
       if(k)
         for(k=0;k<26;k++)
           {
            if(x.a[k])
              change(1,1,p,i,i+x.a[k]-1,k);
            i+=x.a[k];
           }
       else
         for(k=25;k>=0;k--)
           {
            if(x.a[k])
              change(1,1,p,i,i+x.a[k]-1,k);
            i+=x.a[k];
           }
      }
    for(i=1;i<p;i++)
      down(i,1);
    for(i=1;i<=n;i++)
      printf("%c",'a'+g[p+i-1]);
    printf("\n");
    return 0;
}

D3 T2:Matrix
这道题只好意会一下,用 f[i][j] 表示做到前 i列,已经有 j列在右侧区间放 1的方案数。 i 越过一个左侧区间的右端点时,从之前剩下空列中选一个在这个左侧区间放 1。也就是说,在扫到i时,我们只在右端点不大于i的左区间放1,转移时分在右侧区间放1或不放 1。还是好转移的。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll size = 3003; 
const ll mod = 998244353;
ll bucl[size] , bucr[size] , l[size] ,r[size];
ll p[size],inv[size];
ll dp[size][size];
ll n,m;

int main()
{
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++)
    {
        scanf("%lld%lld",&l[i],&r[i]);
        bucl[ l[i] ]++;
        bucr[ r[i] ]++;
    }
    for (ll i=1;i<=m;i++){
        bucl[i]+=bucl[i-1];
        bucr[i]+=bucr[i-1];
    }
    //for (ll i=1;i<=m;i++)printf("%d ",bucl[i]);
    //for (ll i=1;i<=m;i++)printf("%d ",bucr[i]);putchar('\n');
    //inv[1]=1;p[1]=1;
    //for (ll i=2;i<=n;i++) {p[i]= (p[i-1]*i)%mod; inv[i] = (mod-mod/i)*inv[i%mod];}
    (dp[0][0]=1)%=mod;
    for  (ll i=1;i<=m;i++)
    { 
    dp[i][0] = dp[i-1][0]%mod;
        for (ll j = 1;j<= i;j++)
        {
            (dp[i][j] += ( dp[i-1][j-1]* (bucr[i] - j + 1  ) )%mod + dp[i-1][j])%=mod;
        }
        for (ll p = bucl[i-1]+1 ; p <= bucl[i] ; p++)
            for (ll j = 0;j<=i;j++)
                (dp[i][j] *= (i - p - j +1))%=mod; 

    }
    /*for (ll i=1;i<=m;i++)
    {
        for (ll j=0;j<=m;j++)
        printf("%d ",dp[i][j]);
        printf("\n");
    }*/
    printf("%lld",dp[m][n]);
    return 0;
}

D3 T3:Big
我们发现,对手是把我们现在得到的数在二进制下把第一位移到最右边(位数限制为n位的二进制数),又因为我们可以取遍1到 2n1 的数,所以换不换没啥意义,就是要把a序列换一下。问题转化为选一个数,使它左移位后与 m+1 个给定数分别异或的最小值大。然后造字典树,如果当前为0,1都有,则出来只能是0,反之只有一个,则是1。

#include<cstdio>
#include<algorithm>
typedef long long ll;
const ll size = 100005;
int n,m;
int trie[32*size][2] , cnt = 0;
ll ans = 0 , anscnt = 0;
int a[size],b[size];

void insert (int x)
{
    int now = 0;
    for (int i = n ; i >= 1; i--)
    {
        int tmp = ( x & ( 1 << ( i - 1 ) ) ) > 0 ? 1 : 0; 
        if (!trie[now][ tmp ] ) trie[now][tmp] = ++cnt;
        now = trie[now][tmp];
    }
}

int Rieman_Lebel_Change(int x)//黎曼与勒贝尔变换(乱写的)
{
    return ( ( 2 * x ) / ( 1 << n ) + (2 * x) ) % (1 << n );
}

void solve(int dep,int node,ll bigth)
{
    if (dep <= 0)
    {
        if (bigth>ans)
        {
            ans = bigth;
            anscnt = 1;
        }
        else if (bigth==ans) anscnt++;
        return ;
    }
    if (trie[node][1]&&trie[node][0])
    {
        solve(dep-1,trie[node][1],bigth);
        solve(dep-1,trie[node][0],bigth);
    }else
    if (trie[node][1])
    solve(dep-1,trie[node][1], bigth| ( 1 << (dep-1) ) );else
    if (trie[node][0])
    solve(dep-1,trie[node][0], bigth| ( 1 << (dep-1) ) );

}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
    scanf("%d" , &a[i]);
    a[i] ^= a[i-1];
    }
    for (int i = 0;i<=m;i++)
    {
        int tmp = Rieman_Lebel_Change(a[i]) ^ (a[m] ^ a[i]);
        insert(tmp);
     }
    solve(n,0,0);
    printf("%lld\n%lld",ans,anscnt);
}

D4

D4 T1:Mayuri
就是约瑟夫问题,可以有 Ominnm Omlogn 的,又因为m小,所以用第一个好一点。

#include<cstdio>
#include<algorithm>
#include<ctime>
#include<iostream>
using namespace std;
typedef long long ll;
const int size = 10005;
ll dfs2(ll,ll);
ll T,n,m;
ll dfs(ll n) 
{
    if (n==1) return 0;else 
    return    ( dfs( n - 1 )    +  m ) %n;
}
int main()
{ 
    scanf("%lld",&T);    
    while (T--)
    {
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",dfs2(n,m)+1);
    }    

    return 0;    
    }
    ll dfs2( ll n, ll m )
    { 
     ll p = 0;
     if (m==1) return n-1;       
     for (ll i=2;i<=n;i++)
     {
         (p+=m)%=i;    
         ll x = (i-p) / (m-1) ;
      if (x+i<n)
      { i+=x;(p += m*x)%=i;} 
      else{p+=m*(n-i);i=n;}    
     }               

     return p%n; 
    }

D4 T2:Kurisu
化简之后变成这个这里写图片描述
然后bit就好啦。

#include<cstdio>
#include<algorithm>
#include<ctime>
#include<iostream>
using namespace std;
typedef long long ll;
const int size = 1000005;
const ll mod = 20170927;
ll x[size],y[size];
ll x2[size],y2[size],xy2[size],xy[size];
ll n,m,dod;
ll lowbit(ll x){return (x&(-x));}

void update(ll x,ll *c,ll del)
{
    while (x<=n)
    {
        (c[x] += del)%mod;
        x+=lowbit(x);
    }
}

ll query(ll x,ll *c)
{
    ll ans=0;
    while (x)
    {
        (ans+=c[x])%=mod;
        x-=lowbit(x);
    }
    return ans%mod;
}
int main()
{
    //freopen("kurisu.in","r",stdin);
    //freopen("kurisu.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld%lld",&x[i],&y[i]);
        update(i,x2,x[i]*x[i]%mod);
        update(i,y2,y[i]*y[i]%mod);
        update(i,xy,x[i]*y[i]%mod);
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%lld",&dod);
        if (dod==1)
        {
            ll p,xx,yy;
            scanf("%lld%lld%lld",&p,&xx,&yy);
            update(p,x2,xx*xx-x[p]*x[p]);
            update(p,y2,yy*yy-y[p]*y[p]);
            update(p,xy,(xx*yy-x[p]*y[p]+mod)%mod);
            x[p]=xx;y[p]=yy;
        }
        else
        if (dod==2)
        {
            ll l,r;
            scanf("%lld%lld",&l,&r);
            //printf("%lld%lld\n",l,r);
            ll sx = (query(r,x2) - query(l-1,x2))%mod;
            ll sy = (query(r,y2)  -  query(l-1,y2))%mod;
            ll sxy = (query(r,xy) - query(l-1,xy))%mod;
            ll ans  = ( (sx*sy%mod  - sxy*sxy%mod)+mod)%mod;
            (ans +=mod)%=mod;
            printf("%lld\n",ans);         
        }    
    }
    return 0;
}

D4 T3:Okarin
就是一个最长公共上升子序列。
(无输出方案版)

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll size = 5005;
ll dp[size],a[size],b[size];
ll n,m;
int main()
{
    scanf("%lld",&n);
    for (int i=1;i <= n;i++) scanf("%lld",&a[i]);
    scanf("%lld",&m);
    for (int i=1;i <= m;i++) scanf("%lld",&b[i]);
    for (int i=1;i <= n;i++)
    {
        ll maxn=0;
      for (int j = 1 ; j <= m ; j++)
      {
          if (a[i] == b[j]) dp[j] = maxn+1;
          else
          if (a[i] > b[j]) maxn = max(maxn , dp[j]);
      }
    }
    ll ans=0;
    for (ll i = 1 ; i <= m ; i++)
    ans = max(ans , dp[i]);
    printf("%lld",ans);  
}

D5

D5 T1:Adore
就是数位dp,0表示这位是偶数,1表示这位是奇数。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int size = 100005 , mod = 998244353 ;
int f[2][2048];
int pre[20],aft[20],cnt[2048] ;//
int m,k;

int main()
{
    scanf("%d%d",&m,&k);
    int now = 0;
    for (int i = 1 ; i <= k ; i++)
    {
        int tmp;scanf("%d",&tmp);
        now |= tmp << ( i - 1 );
    }
    for (int i = 1 ; i <= 2047 ; i++) cnt[i] = cnt[i >> 1 ] ^ (i & 1);
    int t = 0; f[t][now] = 1 ; int maxn = ( 1 << k ) - 1 ;

    for (int I = 2 ; I <= m-2 ; I++)
    {
        memset(pre , 0 , sizeof pre);memset(aft , 0 , sizeof aft);
        t ^= 1;memset(f[t],0,sizeof f[t]);
        for (int i = 1 ; i <= k ; i++) 
         for (int j = 1 ; j <= k ; j++)
            {
                int tmp; scanf("%d",&tmp);
                pre[j] |=  tmp << ( i - 1 ) ;
                aft[i] |=  tmp << ( j - 1 ) ;            
            }

        for (int i = 0 ; i <= maxn ; i++)
        if (f[t^1][i])
        {
            int s0=0 , s1=0;
            for (int j = 1 ; j <= k ; j++)
            {
                s0 |= cnt[pre[j] & i] << (j-1);
                s1 |= cnt[aft[j] & i] <<(j-1);
            }
            ( f[t][s0] += f[t^1][i] )%= mod;
            ( f[t][s1] += f[t^1][i] )%= mod;
        }    
    }
    int wtf = 0;   
    for (int i=1;i<=k;i++)
    {
        int tmp;scanf("%d",&tmp);
        wtf |= tmp << ( i - 1 ) ;
    }
    int ans = 0;
    for (int i=0;i<=maxn;i++)
    if (cnt[i & wtf ] == 0) (ans += f[t][i])%=mod;
    printf("%d",ans);
  }

D5 T2:Confess
乱搞,随机选n对就好啦(我绝对不会告诉你,倒着搜就不用随机啦)。证明:证明?我们不妨算一算如果随机选两个集合,他们交的期望 min(Σ2ni=1C(ci,2)C(n+1,2)|Σ2ni=1ci=n(n+1))=n12 注意n 是偶数,所以大于 n12 即可能会有交为n 的,由于大了一个常数,所以至少有 n 对!随机O(n) 对即可。
这是STD,我的是倒着搜的,要遭hack,就不放啦。

#include <iostream>
#include <cstdio>
#include <map>
#include <set>
#include <queue>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <bitset>
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define dep(i,a,b) for(int i = a; i >= b; i--) 
#define Rep(i,a) for(int i = 0; i < a; i++)
#define pb(a) push_back(a)
#define mp(a,b) make_pair(a,b)
#define ab(x) ((x) < 0 ? -(x) : (x))
using namespace std;
typedef long long LL;
typedef map<int, int>::iterator mit;
typedef set<int>::iterator sit;
const int N = 6010;
int n;

bitset<2 * N> a[N];

void init() {
    scanf("%d",&n); int m = (n * 2 - 1) / 6 + 1;
    rep(i,1,n + 1) {
        rep(j,1,m) {
            char c = getchar(); while (c < 33) c = getchar();
            c -= 33;
            Rep(d,6) a[i][(j - 1) * 6 + d] = c >> d & 1;
        }
    }
}

bool check() {
    int x = rand() % (n + 1) + 1, y = rand() % n + 1; if (y >= x) y++;
    if ((int)(a[x] & a[y]).count() >= n / 2) { printf("%d %d\n",x,y); return true; } else return false;
}

int main() {
    freopen("confess.in","r",stdin);
    freopen("confess.out","w",stdout);
    srand(233); init();
    int t = 3 * n; if (t < 100) t = 100;
    rep(i,1,t) if (check()) return 0;
    return 0;
}

D5 T3:Repulsed
就是贪心一样的 dp ,我们只配那些距离为k的房间与灭火器。考虑自底向上贪心。 G[x][k] 表示 x 下面距离为k的需要灭火器的房间数, F[x][k] 表示 x 下面距离为k 的多余灭火器数,每个灭火器和房间的匹配在 lca 处理每次假设子树里已经最优了,那么 G[x][k] 一定要用 F[x][0] 填满。

#include<cstdio>
#include<algorithm>
using namespace std;
const int size = 100005;
typedef long long ll;
ll n,s,k;
ll ans = 0;
struct EDGE{
    ll to,next;
}edge[ 2 * size];ll cnt=0;
ll fa[size] , head[size];
ll f[size][30],g[size][30];

void adde(ll fr,ll to)
{
    edge[++cnt]=(EDGE){to,head[fr]};
    head[fr] = cnt;
}
void addedge(ll fr,ll to)
{
    adde(fr,to);
    adde(to,fr);
}

ll calc(ll x) {return (x/s + ( ( x % s ) > 0 ) ); }

ll dfs1(ll node,ll Fe)
{
    fa[node] = Fe;
    for (ll j =head[node];j;j = edge[j].next)
    if (edge[j].to!= Fe) dfs1(edge[j].to,node);
}

ll dfs2(ll node)
{
    for (ll j=head[node];j;j=edge[j].next)
    {
        if (edge[j].to!=fa[node]) 
        {
        dfs2(edge[j].to);
            for (ll i = 1;i <= k;i++)
            {
            f[node][i] = min(n , f[node][i] + f[edge[j].to][i-1]);
            g[node][i] += g[edge[j].to][i-1];
            }
        }
    }
    g[node][0]++;
    if (g[node][k])
    {
        ll need = calc(g[node][k]);
        ans += need;
        f[node][0] = min (need * s , n);
    }
    for (ll i=0;i<=k;i++)
    {
        ll j = min(g[node][i],f[node][k-i]);
        g[node][i] -= j; f[node][k-i] -= j;
    }
    for (ll i=0;i<=k-1;i++)
    {
        ll j = min(g[node][i],f[node][k-1-i]);
        g[node][i] -= j; f[node][k-1-i] -= j;
    }
}
int main()
{
    scanf("%lld%lld%lld",&n,&s,&k);
    for (ll i=1;i<n;i++)
    {
        ll fr,to;
        scanf("%lld%lld",&fr,&to);
        addedge(fr,to);
    }
    dfs1(1,0);
    dfs2(1);
    for (ll i=0;i<=k;i++)
        for (ll j=0;j<=k;j++)
        {
            if (i+j<=k)
            {
                ll need = min(f[1][i],g[1][j]);
                f[1][i] -= need; g[1][j] -= need;
            }    
        }    
    ll cnt=0;
    for (ll i=0;i<=k;i++) cnt+=g[1][i];
    ans += calc(cnt);
    printf("%lld",ans);
}

D6

D6 T1:starway
好题。你考虑如果现在我们定一个答案,那么我们人就可以变成一个园啦,如果走不动,那么拦住他的就是一条路径,那么每个点再与上下边界连表,就变成了最小生成树。这是完全图,用 prim ,然后边存不下,就可以先不存,扫的时候在算,然后更新后才加边。这样还不用打标记,造好了图直接跑就可以啦。

//看我程序多美观 
//美观啊,太美观了!!! 
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define f(x,y,z) for (int (x) = (y) ; (x) <= (z) ; (x)++)
#define d(x,y,z) for (int (x) = (y) ; (x) >= (z) ; (x)--)
using namespace std;
typedef long long ll;
const ll size = 6010;
int n , m , k ;
struct     EDGE {
    ll  to , next;
    ll val;
} edge[2 * size]; ll cnt = 0;

ll d[size];
ll head[size] , pre[size] , x[size] , y[size];
bool vis[size];
ll ans = 0 ;

void adde(ll fr , ll to , ll val)
{
    edge[++cnt] = (EDGE){  to , head[fr] , val};
    head[fr] = cnt;
}
void addedge(ll fr , ll to , ll val)
{
    adde(fr , to , val );
    adde(to , fr , val );
}

ll dis(ll a , ll b)
{
    if (a > b) swap(a , b);
    if (a == k + 1 && b == k + 2) return  1ll * m * m ; 
    else if (b == k + 1) return  1ll * y[a] * y[a] ;
    else if (b == k + 2) return 1ll * ( m - y[a] ) * ( m - y[a] ) ;
    else return 1ll * ( x[a] - x[b] ) * ( x[a] - x[b] ) + 1ll * ( y[a] - y[b] ) * ( y[a] - y[b] ) ;
}

void dfs(ll node , ll f , ll c)
{
    //printf("%d %d\n",node,c);
    if (node == k + 2) { ans = c; return ;}else 
    for (ll j = head[node] ; j ; j = edge[j].next)
    if ( edge[j].to != f) dfs( edge[j].to , node , max( c , edge[j].val ) );        
}

int main()
{
    //freopen("starway.in","r",stdin);
    //freopen("starway23.out","w",stdout);
    scanf("%lld %lld %lld" , &n , &m , &k );
    const ll inf = 1ll * m * m + 10;
    for (ll i = 1 ; i <= k ; i++) scanf("%lld %lld" , &x[i] , &y[i] );
    for (ll i = 1 ; i <= k + 2 ; i++) d[i] = inf;d[k + 1] = 0;
    memset(vis , false , sizeof vis);
    for (ll t = 1 ; t <= k + 2 ; t++)
    {
        ll minm = inf ; ll flag = 0;
        for (ll i = 1 ; i <= k + 2 ; i++) 
            if ( minm > d[i] && !vis[i] )
            {
                minm = d[i];
                flag = i;
            }
        if ( pre[flag] )  addedge(pre[flag] , flag , d[flag]);
        vis[flag] = 1;
        for (ll i = 1 ;i <= k + 2 ; i++)
            if ( dis( flag , i ) < d[i] && !vis[i] )
            {
                d[i] = dis( flag , i );
                pre[i] = flag;
            }
    }
    dfs(k + 1 , 0 , 0);
    printf( "%.9lf\n" , sqrt(ans) / 2 );
    //for (ll i=1;i<=cnt;i++)
    //printf("%d %lld\n",edge[i].to,edge[i].val); 

    return 0;
}

D6 T2:knows
其实就是极长上升序列考虑 O(n2) dp fi 表示左边最后一个选的是谁,每次转移的时候枚举一个前面既不相交,又能保证极长的 j 转移这样的j一定是个上升序列,线段树维护上升序列即可。复杂度为 O(nlog2n)

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int , int> Node;
const int size = 200050;
const int inf = 2002010910;
int n , p[size] , w[size] , f[size];
Node tr[size * 4] , wtf[size * 4];
#define mp make_pair
#define mid ( ( l + r ) >> 1 )
Node operator + (const Node &a , const Node &b){
    if (b.first == inf) return a; else 
    return mp( b.first , min(a.second , b.second) );
}  

void build(int node , int l , int r)
{
    tr[node] = wtf[node]  = mp(inf , inf);
    if (l == r) return ;
    build(node * 2 , l , mid);
    build(node * 2 + 1 , mid + 1 , r);
}

Node calc (int node , int l , int r , Node x)
{
    if (l == r)
    return x > tr[node] ? tr[node] : mp(inf,inf);
    if (tr[node * 2] < x)
    return calc(node * 2 , l , mid , x) + wtf[node]; else
    return calc(node * 2 + 1 , mid + 1 , r , x) ;
}

void update(int node,int l,int r)
{
    wtf[node] = calc(node * 2 + 1 , mid + 1 , r , tr[node*2]);
    tr[node] = tr[node * 2] + wtf[node];
}

void modify(int node,int l,int r,int p,Node x)
{
    if (l == r)
    {
        tr[node] = x;
        return ;
    }
    if (p <= mid)
    modify(node * 2 , l , mid , p , x);
    else
    modify(node * 2 + 1 , mid + 1 , r , p , x);
    update(node , l , r);

}
void query(int node,int l,int r,int ql,int qr,Node &x)
{    
    if (l >= ql && r <= qr){
        x = x+ calc(node , l , r , x);
        return ;
    }
    if (ql <= mid) query(node * 2 , l , mid , ql , qr , x);
    if (qr >= mid + 1) query(node * 2 + 1 , mid + 1 , r , ql , qr , x);
}
#define trr stf
#define node Node
int main()
{
    scanf("%d" , &n);
    build(1, 1, n);
    for (int i = 1; i <= n; i++)
        scanf("%d", p + i);
    for (int i = 1; i <= n; i++)
        scanf("%d", w + i);
    for (int i = n; i >= 1; i--) {
        Node cur = make_pair(inf, inf);
        query(1, 1, n, p[i], n, cur);
        f[i] = (cur.first < inf) ? (w[i] + cur.second) : w[i];
        cur = make_pair(i, f[i]);
        modify(1, 1, n, p[i], cur);
    }
    Node ans = make_pair(inf, inf);
    query(1 , 1 , n, 1, n, ans);
    printf("%d", ans.second);
    return 0;
}

D6 T3:lost
维护凸包,二分弹栈。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll size = 500006;
ll st[size][22];
ll d[size] , c[size] , fa[size] , ans[size];
ll n;

void wei_dfs(ll node)
{
        if (node == 0) d[node] = 0 ; else d[node] = d[fa[node] ] + 1 ;
        ll cur = fa[node] ; 
        for (ll i = 21 ;i >= 0 ; i--)//这里数字只要比 log (n)大就可以了 
        {
            ll to = st[cur][i];if (to == 0) continue;
            ll nowzi = c[to] - c[node] , nowfa = d[node] - d[to];
            ll fazi  = c[ st[to][0] ] - c[node] , fafa  = d[node] - d[ st[to][0] ];
            if (fafa * nowzi >= fazi * nowfa)  cur = st[to][0];
            //(c_v - c_cur) / (d_u - d_v) 的 min) 即 (c_v - c_cur) / (d_v - d_u) 的max  算了,要移项变号 麻烦啊 
        } 

        ll to = cur;
        ll nowzi = c[to] - c[node] , nowfa = d[node] - d[to];
        ll fazi  = c[ st[to][0] ] - c[node] , fafa =  d[node] - d[ st[to][0] ];//此处巨坑 注意是与node比较 而不是单调队列优化那样 害我检查了30min 
        if (fafa * nowzi >= fazi * nowfa) cur = st[to][0];        
        ans[node] = cur ; st[node][0] = cur ;//更新答案 
        for (ll i = 1 ; i <= 21 ; i++)
        st[node][i] = st[ st[node][i-1] ][i - 1];    
}



int main()
{
    //freopen("lost.in","r",stdin);
    //freopen("lost2333.out","w",stdout);
    scanf("%lld",&n);

    for (ll i = 0 ; i < n ; i++)//我发现好像必须要移这个位 
    scanf("%lld" , &c[i]);

    for (ll i = 1 ; i < n ; i++)
    scanf("%lld" , &fa[i]) , fa[i]--;fa[0] = -1;

    for (ll i = 1 ; i < n ; i++)
    wei_dfs(i);
    for (ll i = 1; i < n; ++i)
    printf("%.10lf\n", (double) (c[ans[i]] - c[i]) / (d[i] - d[ans[i]]) );
}

D7

D7 T1 :conjucate
期望可加性,考虑每个在第一个数之前被抓的概率,并且与其他元素无关,有独立性,所以有第i堆的贡献是: aiai+a1

#include<cstdio>
double ans = 1;
long long n,a[100010];
int main()
{
    scanf("%lld",&n);
    for (int i = 1;i<=n;i++)
    scanf("%lld",&a[i]);
    for (int i=2;i<=n;i++)
    ans += (double) a[i]/(double)(a[1]+a[i]);
    printf("%.12lf",ans);
}

D7 T2:conjuct
乱搞dp lcs。

//做不来啦、、、粘的STD
#include <iostream>
#include <cstdio>
#include <map>
#include <set>
#include <queue>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define dep(i,a,b) for(int i = a; i >= b; i--) 
#define Rep(i,a) for(int i = 0; i < a; i++)
#define pb(a) push_back(a)
#define mp(a,b) make_pair(a,b)
#define ab(x) ((x) < 0 ? -(x) : (x))
using namespace std;
typedef long long LL;
typedef map<int, int>::iterator mit;
typedef set<int>::iterator sit;
typedef pair<int, int> pii;
#define x first
#define y second
const int N = 5010, inf = 1e6 + 10;
int T, n, f[N][2][2], g[3][N][2][2], a[N];

void upd(int &a, int b) { a = (b > a) ? b : a;  }

void work() {
    scanf("%d",&n); int tot = 0, c0 = 0, c1 = 0, c2 = 0; 
    rep(i,1,n) scanf("%d",a + i), tot += (a[i] == 2), c0 += (a[i] == 0), c1 += (a[i] == 1), c2 += (a[i] == 2);
    if (!c0 || !c1) { printf("0\n"); return; }
    if (!c2) { printf("-1\n"); return; }
    rep(i,0,2) rep(j,0,n) Rep(d0,2) Rep(d1,2) g[i][j][d0][d1] = -inf;
    g[2][0][0][0] = 0;
    int ans = 0; 
    rep(i,1,n) {
        rep(j,0,n) Rep(d0,2) Rep(d1,2) f[j][d0][d1] = -inf;
        Rep(d,3) rep(j,0,i - 1) Rep(d0,2) Rep(d1,2) {

            int D0 = d0 || (a[i] == 0), D1 = d1 || (a[i] == 1);
            if (d == 2 && a[i] == 2) D0 = true, D1 = true;

            if ((a[i] ^ d) == 1 || (a[i] == 2)) 
                f[j + 1][D0][D1] = max(f[j + 1][D0][D1], g[d][j][d0][d1] + 1);
            else f[j][D0][D1] = max(f[j][D0][D1], g[d][j][d0][d1] + 1);
        }
        rep(j,0,i) Rep(d0,2) Rep(d1, 2) g[a[i]][j][d0][d1] = max(g[a[i]][j][d0][d1], f[j][d0][d1]);
        rep(j,0,tot) ans = max(ans, max(f[j][0][0], f[j][1][1]));
        rep(j,0,tot - 1) ans = max(ans, max(f[j][0][1], f[j][1][0]));
        if (a[i] == 2) ans = max(ans, max(f[tot][0][1], f[tot][1][0]));
    }
    printf("%d\n",n - ans);
}

int main() {
    scanf("%d",&T);
    while (T--) work();
    return 0;
}

D7 T3:conjecture
乱搞。

D8

D8 T1 graph:
贪心,把图建成树,然后每次贪心取,注意到我们应该在这个点的边数为偶数时全取,奇数时向下再取一个。

#include<cstdio>
#include<vector>
using namespace std;
const int maxn = 100050;
struct edge {
    int to, vis, opp;
};
struct node {
    int to, real;
};
vector<edge> G[maxn];
vector<node> F[maxn];
int vis[maxn];
int siz[maxn];
int ans[maxn][3], ansn;
int n, m;
int read() {
    int a = 0, c = 0, w = 1;
    while(c < '0' || c > '9') {if(c == '-') w = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + c - 48; c = getchar();}
    return a * w;
}

int dfs1(int x) {
    vis[x] = 1;
    F[x].clear();
    for(int i = 0; i < G[x].size(); i++) if(!G[x][i].vis) {
        G[x][i].vis = 1;
        G[G[x][i].to][G[x][i].opp].vis = 1;
        if(!vis[G[x][i].to]) {
            F[x].push_back((node){G[x][i].to, -1});
            siz[x] += dfs1(G[x][i].to) + 1;
        }else{
            F[x].push_back((node){n, G[x][i].to});
            siz[x] += 1;
        }
    }
    return siz[x];
}
void dfs2(int x, int odd, int fa) {
    if(odd == 0) {
        int single = 0, p;
        for(int i = 0; i < F[x].size(); i++) {
            int v = F[x][i].to;
            if(siz[v] % 2 == 0) {
                single++;
                if(single == 1) p = (F[x][i].real != -1 ? F[x][i].real : v);
                else{
                    single = 0;
                    ans[ansn][0] = (F[x][i].real != -1 ? F[x][i].real : v);
                    ans[ansn][1] = x;
                    ans[ansn++][2] = p;
                }
                dfs2(v, 0, x);
            }else dfs2(v, 1, x);
        }
    }else{
        int hungry = 1, single = 0, p;
        for(int i = 0; i < F[x].size(); i++) {
            int v = F[x][i].to;
            if(hungry && siz[v] % 2 == 0) {
                hungry = 0;
                ans[ansn][0] = fa;
                ans[ansn][1] = x;
                ans[ansn++][2] = (F[x][i].real != -1 ? F[x][i].real : v);
                dfs2(v, 0, x);
            }else if(siz[v] % 2 == 0) {
                single++;
                if(single == 1) p = (F[x][i].real != -1 ? F[x][i].real : v);
                else{
                    single = 0;
                    ans[ansn][0] = (F[x][i].real != -1 ? F[x][i].real : v);
                    ans[ansn][1] = x;
                    ans[ansn++][2] = p;
                }
                dfs2(v, 0, x);
            }else dfs2(v, 1, x);
        }
    }
}

int main() {
    n = read(); m = read();
    for(int i = 0; i < m; i++) {
        int from = read()-1, to = read()-1;
        G[from].push_back((edge){to, 0, G[to].size()});
        G[to].push_back((edge){from, 0, G[from].size()-1});
    }
    for(int i = 0; i < n; i++) if(!vis[i]) {
        dfs1(i);
        dfs2(i, 0, -1);
    }
    printf("%d\n", ansn);
    for(int i = 0; i < ansn; i++) printf("%d %d %d\n", ans[i][0]+1, ans[i][1]+1, ans[i][2]+1);
    return 0;
}

D8 T2 permutation:
注意到我们先反一下原数列,构造 p[i] 表示i现在在原来的那个数列,所以现在交换就变成了相邻交换。然后有些距离小于 k <script type="math/tex" id="MathJax-Element-87">k</script>的两个的相对位置不会变,所以靠此建图,拓扑排序,dfs一遍就好了。

#include<bits/stdc++.h>
using namespace std;
int a[500010],pos[500010];
bool fl;


const int maxn=500010;
int n,k,p[maxn],q[maxn],deg[maxn];
int tote,FIR[maxn],TO[maxn<<1],NEXT[maxn<<1];
priority_queue<int> pq;

namespace SegTree{
    int mn[maxn<<2];
#define lc (nd<<1)
#define rc (nd<<1|1)
#define mid ((s+t)>>1)

    void init()
    {
        memset(mn,0x3f3f3f3f,sizeof(mn));
    }

    void update(int nd,int s,int t,int id,int val)
    {
        if (s==t) {mn[nd]=val; return;}
        if (id<=mid) update(lc,s,mid,id,val);
        else update(rc,mid+1,t,id,val);
        mn[nd]=min(mn[lc],mn[rc]);
    }

    int query(int nd,int s,int t,int l,int r)
    {
        if (l<=s&&t<=r) return mn[nd];
        int Ans=0x7fffffff;
        if (l<=mid) Ans=min(Ans,query(lc,s,mid,l,r));
        if (r> mid) Ans=min(Ans,query(rc,mid+1,t,l,r));
        return Ans;
    }
}

void addedge(int u,int v)
{
    TO[++tote]=v;
    NEXT[tote]=FIR[u];
    FIR[u]=tote;
    deg[v]++;
}
void read(int &x)
{
    char c=getchar(); x=0;
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
}
int main()
{
    //freopen("permutation.in","r",stdin);
    //freopen("permutation.out","w",stdout);
    scanf("%d",&n);scanf("%d",&k);
    if (n<=10000)
    {
    for(int i=1;i<=n;++i)scanf("%d",&a[i]),pos[a[i]]=i;
    if(k==1)
    {
        for(int i=1;i<=n;++i)printf("%d\n",i);
        return 0;
    }
    fl=1;
    while(fl)
    {
        fl=0;
        for(int i=1;i<=n;++i)
        {
            for(int j=a[i]-1;j>0;--j)
            {
                if(pos[j]<i||pos[j]-i<k)break;
                if(!fl)fl=1;
                swap(pos[a[i]],pos[j]);
                swap(a[pos[a[i]]],a[pos[j]]);
            }    
        }
    }
    for(int i=1;i<=n;++i)printf("%d\n",a[i]);
    }
    else
    {int i,x;
        for (i=1;i<=n;i++)
        read(p[i]),q[p[i]]=i;
    SegTree::init();
    for (i=n;i>=1;i--)
    {
        x=SegTree::query(1,1,n,q[i]-k+1,q[i]);
        if (x<=n) addedge(q[x],q[i]);
        x=SegTree::query(1,1,n,q[i],q[i]+k-1);
        if (x<=n) addedge(q[x],q[i]);
        SegTree::update(1,1,n,q[i],i);
    }
    for (i=1;i<=n;i++)
        if (!deg[i]) pq.push(i);
    for (i=n;i>=1;i--)
    {
        int u=p[i]=pq.top(); pq.pop();
        for (int p=FIR[u];p;p=NEXT[p])
            if (!(--deg[TO[p]])) pq.push(TO[p]);
    }
    for (i=1;i<=n;i++) q[p[i]]=i;
    for (i=1;i<=n;i++) printf("%d\n",q[i]);

    }
}

D8 T3 tree:
每条边都至少出现了一次,所以最优的情况就是所有边权之和,然后能不能取到证不来,反正可以,所以加起来就好啦。(注意爆longlong)

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
typedef long long ll;
ll n,k;
ll ans=0;
int main()
{
    scanf("%lld",&n);
    ll fr,to,val;
    for (int i=1;i<n;i++)
    scanf("%lld%lld%lld",&fr,&to,&val) ,ans+=val;
    printf("%lld",ans);
    return 0;
}

总结:

这次去雅礼,总的考的不好,也许是因为是高一不稳定的原因。所以回来后要好好提升自己的知识水平,下次去AK雅礼集训。

添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值