noip模拟5

T1

考试时间全砸在这题上了QAQ
结果没调出来线段树
40分很好拿,直接sort就可,打桶排也可,然而我打的挂了10分
100分做法:线段树
容易想到的是用线段树去维护区间内每个字母出现的次数,要开26颗线段树,常数还有点大。
所以换一种方法,用线段树去维护每个节点是否相等,相等的话,就返回区间大小。然后就是喜闻乐见的区间修改和区间查询。
其实桶排也可,但本人太弱,所以直接放blog

Code
#include<cstdio>
#define MAX 100001
#define re register
namespace OMA
{
   int n,m;
   int cnt[27];
   char s[MAX];
   struct Segment
   {
     struct TREE
     {
       int ch;
       int l,r;
     }tree[MAX<<2];
     inline int ls(int now)
     { return now<<1; }
     inline int rs(int now)
     { return now<<1|1; }
     inline void Push_up(int now)
     { tree[now].ch = (tree[ls(now)].ch==tree[rs(now)].ch)?tree[ls(now)].ch:0; }
     inline void Push_down(int now)
     { if(tree[now].ch){ tree[ls(now)].ch = tree[rs(now)].ch = tree[now].ch; } }
     inline void build(int now,int l,int r)
     {
       tree[now].l = l,tree[now].r = r;
       if(l==r)
       { tree[now].ch = s[l]-'a'+1; return ; }
       int mid = (l+r)>>1;
       build(ls(now),l,mid),build(rs(now),mid+1,r);
       Push_up(now);
     }
     inline void query(int now,int l,int r)
     {
       if(l<=tree[now].l&&tree[now].r<=r&&tree[now].ch)
       { cnt[tree[now].ch] += tree[now].r-tree[now].l+1; return ; }
       Push_down(now);
       int mid = (tree[now].l+tree[now].r)>>1;
       if(l<=mid)
       { query(ls(now),l,r); }
       if(r>mid)
       { query(rs(now),l,r); }
     }
     inline void update(int now,int l,int r,int pos)
     {
       if(l<=tree[now].l&&tree[now].r<=r)
       {  tree[now].ch = pos; return ; }
       Push_down(now);
       int mid = (tree[now].l+tree[now].r)>>1;
       if(l<=mid)
       { update(ls(now),l,r,pos); }
       if(r>mid)
       { update(rs(now),l,r,pos); }
       Push_up(now);
     }
     inline void print(int now)
     {
       if(tree[now].ch)
       {
         for(re int i=tree[now].l; i<=tree[now].r; i++)
         { printf("%c",tree[now].ch+'a'-1); }
         return ;
       }
       print(ls(now)),print(rs(now));
     }
   }Tree;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   signed main()
   {
     n = read(),m = read();
     scanf("%s",s+1);
     Tree.build(1,1,n);
     for(re int i=1; i<=m; i++)
     {
       int l = read(),r = read(),x = read();
       int temp = l;
       for(re int j=1; j<=26; j++)
       { cnt[j] = 0; }
       Tree.query(1,l,r);
       if(x)
       {
         for(re int j=1; j<=26; j++)
         {
           if(cnt[j])
           { Tree.update(1,temp,temp+cnt[j]-1,j); temp += cnt[j]; }
         }
       }
       if(!x)
       {
         for(re int j=26; j>=1; j--)
         {
           if(cnt[j])
           { Tree.update(1,temp,temp+cnt[j]-1,j); temp += cnt[j]; }
         }
       }
     }
     Tree.print(1);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T2

神仙dp题
下午letitdown讲题时说,把矩阵倒过来,给我听傻了
直接状压可拿20分
100分做法:
首先要明白,左右区间在那一列是没有区别,我们要的是在i点及其之前有多少左区间端点和多少右区间端点,我们分别用 \(l_{i}\)\(r_{i}\) 来表示。
接下来考虑dp,设 \(dp_{i,j}\) 表示前i列中有j个1放在右区间中,考虑以下几种情况:

当我们dp到一个新的状态,即一个新的 \(i,j\),我们需要去维护该状态是否合法。

\(i-j-l_{i-1}\) 为左区间最多能放的个数,\(l_{i}-l_{i-1}\) 为在区间中必须要放的个数,显然,\(A_{n}^{m}\) 如果 \(m>n\) 会返回0,即可判定此状态非法,同时该状态对其他状态的贡献也为0

另一种解释,还是dp到了一个新的状态,我们开始考虑左区间,考虑什么样的左区间? 因为我们在i之前的已经考虑过了,而在i之后的现在还不用去考虑,所以我们只需要去考虑端点在i上的左区间即可,那么则需要在前i列里选出满足还没放1,同时端点又在第i列的左区间去放1,前者即为 \(i-j-l_{i-1}\),后者即为 \(l_{i}-l_{i-1}\)

\[dp\left(i,j\right)\times=A_{i-j-l_{i-1}}^{l_{i}-l_{i-1}} \]

空着不放1时,直接转移即可

\[dp\left(i+1,j\right)+=dp\left(i,j\right) \]

当放在右区间中时,在前i+1列,则有 \(r_{i+1}-j\) 个区间没有放1,则

\[dp\left(i+1,j+1\right)+=dp\left(i,j\right)\times \left(r_{i+1}-j\right) \]

关于第一个方程中为什么是排列而不是组合,请参考此篇blog

Code
#include<cstdio>
#define MAX 3010
#define re register
#define int long long
namespace OMA
{
   int n,m;
   int dp[MAX][MAX];
   int l[MAX],r[MAX];
   int c[MAX],inv[MAX];
   const int p=998244353;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline int quickpow(int a,int b)
   {
     int ans = 1;
     while(b)
     {
       if(b&1)
       { ans = ans*a%p; }
       a = a*a%p;
       b >>= 1;
     }
     return ans;
   }
   void begin()
   {
     c[0] = inv[0] = 1;
     for(re int i=1; i<=m; i++)
     { c[i] = i*c[i-1]%p; }
     inv[m] = quickpow(c[m],p-2);
     for(re int i=m-1; i>=1; i--)
     { inv[i] = (i+1)*inv[i+1]%p; }
   }
   inline int A(int n,int m)
   { return c[n]*inv[n-m]%p; }
   signed main()
   {
     n=read(),m=read();
     begin();
     for(re int i=1; i<=n; i++)
     { l[read()]++,r[read()]++; }
     for(re int i=1; i<=m; i++)
     { l[i] += l[i-1],r[i] += r[i-1]; }
     dp[0][0] = 1;
     for(re int i=0; i<=m; i++)
     {
       for(re int j=0; j<=i; j++)
       {
         if(i)
         { dp[i][j] = (dp[i][j]*A(i-j-l[i-1],l[i]-l[i-1]))%p; }
         dp[i+1][j] = (dp[i+1][j]+dp[i][j])%p;
         dp[i+1][j+1] = (dp[i+1][j+1]+dp[i][j]*(r[i+1]-j))%p;  
       }
     }
     printf("%lld\n",dp[m][n]);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T3

先%一波yspm
难点在于题目中所给式子的转换,先看题目中的式子 \(x=\left(\left\lfloor\frac{2x}{2^{n}}\right\rfloor+2x\right)\mod2^{n}\) 其实就是逻辑左移然而我并没有看出来

\(\left\lfloor\frac{2x}{2^{n}}\right\rfloor\)\(x\gg\left(n-1\right)\) 取第n位,加上 \(2x\)\(x\ll1\) 将x左移1位,后再模一下 \(2^{n}\) 就是取后n位,所以啊,这波操作,就是将x二进制的最高位移至最低位。

\(pre_{i}\) 为操作后的前缀异或和,\(suf_{i}\) 为给的 \(a_{i}\) 的后缀异或和,所以对手给你操作后,将相当于i将你的x异或了个 \(pre_{i}\oplus suf_{i}\),一共有m+1个这玩意。

然后就是喜闻乐见的01trie,从根节点开始dfs遍历
则会有以下三种情况:

该节点有两个子节点,即0和1,那么你无论选那个,对手都会给你来一波操作,让你变成0,此时对答案没有贡献,直接向下dfs

该节点只有一个子节点,则考虑其对答案的贡献,无论是0还是1,贡献都为 \(2^{n-dep-1}\)\(dep\) 为遍历到现在的深度

该节点为叶子节点,那就该更新答案了,如果与记录的最大值相等,那就给数量+1,如果比记录的最大值要大,就更新最大值,同时将答案数量要重置为1

Code
#include<cstdio>
#define MAX 100001
#define re register
namespace OMA
{
   int n,m;
   int ans1,ans2;
   int a[MAX],bin[MAX]={1};
   int x[MAX],pre[MAX],suf[MAX];
   struct Trie
   {
     int tot;
     int ch[MAX*31][2];
     inline void insert(int x)
     {
       int u = 0;
       for(re int i=n-1; i>=0; i--)
       {
         int pos = (x>>i)&1;
         if(!ch[u][pos])
         { ch[u][pos] = ++tot; }
         u = ch[u][pos];
       }
     }
     inline void dfs(int u,int ans,int dep)
     {
       if(ch[u][0]&&ch[u][1])
       { dfs(ch[u][0],ans,dep+1),dfs(ch[u][1],ans,dep+1); return ; }
       if(!ch[u][0]&&!ch[u][1])
       {
         ans2 += (ans==ans1)?1:0;
         if(ans>ans1)
         { ans1 = ans, ans2 = 1; }
         return ;
       }
       if(!ch[u][0]||!ch[u][1])
       { dfs(ch[u][0]|ch[u][1],ans+bin[n-dep-1],dep+1); return ; }
     }
   }tree;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   signed main()
   {
     n=read(),m=read();
     for(re int i=1; i<=n; i++)
     { bin[i] = bin[i-1]<<1; }
     for(re int i=1; i<=m; i++)
     { x[i] = (2*(a[i] = read())/bin[n]+2*a[i])%bin[n]; }
     for(re int i=1; i<=m; i++)
     { pre[i] = pre[i-1]^x[i]; }
     for(re int i=m; i>=1; i--)
     { suf[i] = suf[i+1]^a[i]; }
     for(re int i=0; i<=m; i++)
     { tree.insert(pre[i]^suf[i+1]); }
     //for(re int i=0; i<=m; i++)
     //{ printf("%d ",pre[i]^suf[i+1]); }
     tree.dfs(0,0,0);
     printf("%d\n%d\n",ans1,ans2);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T4

咕咕咕

posted @ 2021-06-07 16:56  -OMA-  阅读(106)  评论(1编辑  收藏  举报
浏览器标题切换
浏览器标题切换end