HDU4747

很好的题,可以用线段树做,也可以递推(dp?)

Mex

HDU - 4747

题意:求所有区间的mex和。mex值为没有在该区间出现过的最小非负整数。

先用比较好理解的线段树:

蒟蒻参考了多位dalao的题解,这里就不放链接了。。。

想法是每次求以i为起点的区间的mex值的和,最后累加即为答案。

可以先预处理出1为起点的区间的mex值,用它构造一棵线段树,要有求和和求最大值的操作。

为什么要求最大值呢? 往下看~

构造好线段树之后,1为起点的区间的mex和已经得到了,就是sum[1]。

接下来求2为起点的,首先第一步要删掉a[1],这里把mex[1]置为0。

删掉val=a[1]这个元素对后面的有什么影响呢?易知它不会对下一个出现val(下一个出现val的位置是nex[1])之后的位置产生影响,因为后面的区间已经包含val了(不少它这一个)

然后我们需要求得大于val的最大mex的位置pos,线段树单点查询最大值即可得到。

对于pos到nex[1] - 1的元素,所有的mex值都要变成a[i](不难想),成段更新线段树即可。

这样就得到了2为起点的区间的mex和,sum[1]。

下面就是重复了~~

代码中有几处很巧妙的地方,要细细领会~

  1 #include <bits/stdc++.h>
  2 using namespace std;
  3 const int maxn=200010;
  4 #define CLR(m,a) memset(m,a,sizeof(m))
  5 #define ll long long
  6 #define lson l,m,rt<<1
  7 #define rson m+1,r,rt<<1|1
  8 int maxmex[maxn<<2],_set[maxn<<2];
  9 ll sum[maxn<<2];
 10
 11 int mex[maxn],vis[maxn];
 12 int head[maxn],nex[maxn];
 13 int a[maxn];
 14
 15 void pushup(int rt){
 16     sum[rt]=sum[rt<<1]+sum[rt<<1|1];
 17     maxmex[rt]=max(maxmex[rt<<1],maxmex[rt<<1|1]);
 18 }
 19 void build(int l,int r,int rt){
 20     _set[rt]=0;
 21     if(l==r){
 22         sum[rt]=maxmex[rt]=mex[l];
 23         return ;
 24     }
 25     int m=(l+r)>>1;
 26     build(lson);
 27     build(rson);
 28     pushup(rt);
 29 }
 30 void pushdown(int rt,int len){
 31     if(_set[rt]){
 32         _set[rt<<1]=_set[rt<<1|1]=1;
 33         sum[rt<<1]=maxmex[rt]*1ll*(len-(len>>1));
 34         sum[rt<<1|1]=maxmex[rt]*1ll*(len>>1);
 35         maxmex[rt<<1]=maxmex[rt<<1|1]=maxmex[rt];
 36         _set[rt]=0;
 37     }
 38 }
 39 void update(int L,int R,int v,int l,int r,int rt){
 40     if(L<=l&&r<=R){
 41         _set[rt]=1;
 42         sum[rt]=1ll*(r-l+1)*v;
 43         maxmex[rt]=v;
 44         return ;
 45     }
 46     pushdown(rt,r-l+1);
 47     int m=(l+r)>>1;
 48     if(L<=m) update(L,R,v,lson);
 49     if(R>m) update(L,R,v,rson);
 50     pushup(rt);
 51 }
 52
 53 int query(int x,int l,int r,int rt){
 54     if(l==r){
 55         return l;
 56     }
 57     pushdown(rt,r-l+1);
 58     int m=(l+r)>>1;
 59     int ans;
 60     if(x<maxmex[rt<<1]) ans=query(x,lson);
 61     else ans=query(x,rson);
 62     return ans;
 63 }
 64
 65
 66 int main(){
 67     int n;
 68     while(scanf("%d",&n)!=EOF&&n){
 69         CLR(vis,0);
 70         int k=0;   //没有这个优化就会超时,,差距甚大!!!
 71         for(int i=1;i<=n;i++){
 72             scanf("%d",&a[i]);
 73             if(a[i]>n) a[i]=n+1;   //!!!
 74             vis[a[i]]=1;
 75             for(int j=k;j<=n;j++)if(!vis[j]){
 76                 mex[i]=j;
 77                 k=j;
 78                 break;
 79             }
 80         }
 81         CLR(head,0x3f);   //!!!
 82         for(int i=n;i>0;i--){
 83            nex[i]=head[a[i]];
 84            head[a[i]]=i;
 85         }
 86        // for(int i=1;i<=n;i++) printf("%d ",nex[i]);puts("");
 87         build(1,n,1);
 88         ll ans=sum[1];
 89         for(int i=1;i<=n;i++){
 90             update(i,i,0,1,n,1);
 91             if(maxmex[1]>a[i]){
 92                 int pos=query(a[i],1,n,1);
 93                 if(pos<nex[i]) update(pos,nex[i]-1,a[i],1,n,1);
 94             }
 95             ans+=sum[1];
 96         }
 97         printf("%lld\n",ans);
 98     }
 99     return 0;
100 }

下面是递推的方法:

感谢大神的分析http://blog.csdn.net/cc_again/article/details/11856847,可是对我等蒟蒻还是难以理解~~

又想了很久才有点明白orz▄█?█●,模拟一波~

我们求以i结尾的区间的mex和f[i],再累加就是答案。为什么要这样求呢?因为可以从f[i-1]推出f[i]。记f[i] = f[i-1] +temp 。

初始temp=0,下面求temp

现在看求第f[i]的过程,现在数列中加入了新的数val=a[i]

首先可以想到的是,对于上一个出现val的位置p(即代码里的last[a[i]]),p之前的元素(即f[p-1],f[p-2],…… f[1])对f[i]的结果没有任何影响(因为在a[i]之前的区间里已经有val了)

现在考虑p到i之间的元素,可以知道,他们中可能有  因为没有val这个元素  而导致 到f[i-1]的mex是比小于等于val的,而现在加入了val,那么它的mex就会增大

那么加入val之后都对哪些元素有影响呢?

这里定义一个覆盖:如果j到i之间出现了0到x的所有值,那么称x的 覆盖为j。

对于新加入的val,求出val-1的覆盖j,那么对于p(上一个val的位置)+1到  min ( j , last[val] )之间的元素,他们到a[i]=val这个位置的mex都【至少】是val+1,本来他们到a[i-1]的mex是val,即都变大了1。此时temp += min ( j , last[val] )  - p。

上面为什么说至少呢?说明还有可能大于val+1。

举个例子,6  4  0  2 1 5  3  。现在处理第七位3, f[7] = f[6] + temp  = 9 + temp 。

加入3之后,根据上面的步骤,我们先求得2的覆盖是3,所以1到3的mex至少变成了4(他们在加入3之前是3),但是显然6的mex是7,4的mex是6,这一步只使得a[3]的mex值正确了。 此时temp +=3。 temp=3。

这是因为前面已经出现了4,5,6,在加入3之前他们没有任何,加入3导致他们也有了作用。这就等价于我们又加入了4,5,6

所以我们还要求3的覆盖,4的覆盖,5的覆盖……,直到mex值完全正确。

步骤如下:

加入4,last[4]=2,3的覆盖是3,所以1到2 ( min(3,last[4] ) 的mex变成了5,  此时temp +=2。 temp=5。

继续,加入5,last[5]=6,4的覆盖是2,所以1到2 ( min(2,last[5] ) 的mex变成了6, 此时temp +=2。 temp=7。

继续,加入6,last[6]=1,5的覆盖是1,所以1到1 ( min(2,last[5] ) 的mex变成了7, 此时temp +=1。 temp=8。

到此就结束了。

所以f[i] = f[i-1] +temp = 9 + 8 =17 。

下面代码里的mp就是上面分析中说到的覆盖。

 1 #include <bits/stdc++.h>
 2 #define ll long long
 3 using namespace std;
 4 int a[200002],last[200002],mp[200002];
 5 int main()
 6 {
 7     int n,cnt;
 8     while(scanf("%d",&n)&&n){
 9             for(int i=1;i<=n;i++)
10                   scanf("%d",&a[i]);
11
12             ll ans=0;
13             ll temp=0,pre;
14             memset(last,0,sizeof(last));
15             memset(mp,0,sizeof(mp));
16             for(int i=1;i<=n;i++){
17                   if(a[i]<=n){   //如果大于n,不会影响任何元素,mex的和不变
18                         pre=last[a[i]];
19                         last[a[i]]=i;
20                         for(int j=a[i];j<=n;j++){
21                               if(j) mp[j]=min(mp[j-1],last[j]);
22                               else mp[j]=last[j];
23                               if(mp[j]>pre){
24                                     temp+=mp[j]-pre;
25                               }else break;
26                         }
27                   }
28                   ans+=temp;
29             }
30             printf("%I64d\n",ans);
31     }
32     return 0;
33 }

时间: 2024-12-13 22:51:58

HDU4747的相关文章

hdu4747(线段树区间更新)

Mex Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Total Submission(s): 1892    Accepted Submission(s): 625 Problem Description Mex is a function on a set of integers, which is universally used for impartial game

HDU-4747 Mex(线段树区间更新)

题目大意:给一个长度为n的整数序列,定义mex(i,j)表示区间[i,j]中没有出现过的最小非负整数,求sigma(mex(i,j)),即序列中所有连续非空子区间的mex之和. 题目分析: answer=mex(1,1)+mex(1,2)...mex(1,n) + mex(2,2)...mex(2,n) . . . + mex(n,n). 初始时,用线段树的叶子节点维护mex(1,i),将a(1)从序列中拿去后,将叶子节点的维护信息更新为mex(2,i),以此类推...没更新一次,便求一次区间和

hdu4747 mex 线段树

题意:给一个序列不超过200000个元素,定义mex(i,j)是区间[i,j]之间所没有的最小非负整数.求sum(mex[i,j])对于所有1<=i<=j<=n; 解法:线段树.先求出mex(1,1),mex(1,2),mex(1,3)...mex(1,n) 而且这必然是递增的. 然后 sum[i=1,1<=j<=n]就算出来了,然后去掉arr[1],这时候会影响到的是下一个arr[1]出现前mex值大于arr[1]的那些位置,而且由于mex具有单调性,如果有必然是连续的一个

HDU4747:Mex(线段树区间修改)

传送门 题意: 给出\(n\)个数,然后求\(\sum_{i=1}^n\sum_{j=i}^nmex(i,j)\).\(mex(i,j)\)表示区间\([i,j]\)的\(mex\). 思路: 考虑枚举左右端点的其中一个,然后快速统计答案. 观察发现对于一个\(a_i\),如果区间左端点从包含它到了不包含的状态,那么其会影响\([i+1,next[a_i]-1]\)这个区间中的\(mex\)值. 那么尝试枚举左端点,根据左端点数值快速统计答案.(一开始想的右端点半天出不来啊= ,=) 怎么统计呢

【线段树】HDU 4747 MEX

通道:http://acm.hdu.edu.cn/showproblem.php?pid=4747 题意:mex(L, R)表示区间上第一个没出现的最小非负整数,对于序列a[],求所有的mex(L, R)的和 思路:就是求mex(1,1) + mex(1,2)+....+mex(1,n) +mex(2,2) + mex(2,3) + ...mex(2,n) +mex(3,3) + mex(3,4)+...+mex(3,n) ...+ mex(n,n) 可以知道mex(z,i),mex(z,i+1