T1 辣鸡(ljh)
又是一道模拟题,我们所要做的就是用简单的$for$循环把各种情况枚举出来,暴力计算答案,就能A了,复杂度$O(n^2)$(其实也不严格,中间有一些小减枝)。
在这道题中,具体的,我们可以把每个无重叠格子对答案的贡献看成两部分:
- 方块内部的贡献。
- 方块间的贡献。
这时候问题就被简化了。
方格内部的贡献,我们可以转化成一个神奇的公式:$ res_i=(lenx-1) imes (leny-1) $(当时我是没想出来,直接暴力加)。
然后计算之间贡献,之间的贡献只与最外层的一条边有关,所以枚举每一个块,计算他们两个之间块的贡献即可,不再细说。
可是这样会$TLE$,我们应该想一个强力一点的剪枝。
我们可以$sort$一下方块,使他们的横坐标升序排列,因此便得到了:只要当前节点的右端小于比较节点的左端-1,那之后的所有点都不再跟当前节点接触,直接$break$。
那么这道题就完美的解决了,复杂度$O(n^2)$,比较简单的一道题。
小弟不才。
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #define int long long 5 #define HZOI using namespace std 6 HZOI; 7 struct node{ 8 int lx,ly,rx,ry,dx,dy; 9 friend bool operator < (node a,node b) 10 { 11 return (a.lx^b.lx)?a.lx<b.lx:a.ly<b.ly; 12 } 13 }kuai[100003]; 14 int n,ans1,ans2; 15 inline int min(int a,int b) { return a<b?a:b; } 16 inline int max(int a,int b) { return a>b?a:b; } 17 signed main() 18 { 19 scanf("%lld",&n); 20 for (int i=1,lx,ly,rx,ry; i<=n; ++i) 21 { 22 scanf("%lld%lld%lld%lld",&lx,&ly,&rx,&ry); 23 int dx=rx-lx+1,dy=ry-ly+1,res=0; 24 kuai[i].lx=lx,kuai[i].rx=rx,kuai[i].ly=ly,kuai[i].ry=ry; 25 kuai[i].dx=dx,kuai[i].dy=dy; 26 /* for (int j=1; j<min(dy,dx); ++j) 27 res+=j; 28 ans1+=(res<<1); 29 ans1+=(max(dy,dx)-min(dy,dx)-1)*min(dy-1,dx-1);*/ 30 ans1+=(dx-1)*(dy-1); 31 } 32 ans1<<=1; 33 sort(kuai+1,kuai+n+1); 34 for (int i=1; i<=n; ++i) 35 { 36 int ux=kuai[i].dx,uy=kuai[i].dy; 37 int ux1=kuai[i].lx,ux2=kuai[i].rx,uy1=kuai[i].ly,uy2=kuai[i].ry; 38 for (int j=i+1; j<=n; ++j) 39 { 40 int vx=kuai[j].dx,vy=kuai[j].dy; 41 int vx1=kuai[j].lx,vx2=kuai[j].rx,vy1=kuai[j].ly,vy2=kuai[j].ry; 42 if (ux2<vx1-1) break; 43 if (uy2==vy1-1 or uy1==vy2+1) 44 { 45 if (vx1<ux1 and vx2>ux2) 46 {ans2+=(ux<<1);} 47 else if (vx1>ux1 and vx2<ux2) 48 {ans2+=(vx<<1);} 49 else if (ux1>vx2+1 or ux2<vx1-1) continue; 50 else if (ux1==vx2+1) ++ans2; 51 else if (ux2==vx1-1) ++ans2; 52 else 53 { 54 int jiao=min(ux2-vx1+1,vx2-ux1+1); 55 ans2+=(jiao<<1); 56 if (ux1==vx1) --ans2; 57 if (ux2==vx2) --ans2; 58 } 59 } 60 else if (ux2==vx1-1 or ux1==vx2+1) 61 { 62 if (vy1<uy1 and vy2>uy2) 63 {ans2+=(uy<<1);} 64 else if (vy1>uy1 and vy2<uy2) 65 {ans2+=(vy<<1);} 66 else if (uy1>vy2+1 or uy2<vy1-1) continue; 67 else if (uy1==vy2+1) ++ans2; 68 else if (uy2==vy1-1) ++ans2; 69 else 70 { 71 int jiao=min(uy2-vy1+1,vy2-uy1+1); 72 ans2+=(jiao<<1); 73 if (uy1==vy1) --ans2; 74 if (uy2==vy2) --ans2; 75 } 76 } 77 } 78 } 79 printf("%lld ",ans1+ans2); 80 return 0; 81 }
T2 模板(ac)
挺难的一道题,实力最多拿到70。
很容易看出来这道题是一颗线段树,因为之前也做过一道类似的题。
也是之前的那道题救了我,让我很快的想到了线段树合并。
不过这道题肯定不是线段树合并,但是可以得到40分,加上暴力可以得到的30分,已经是考试极限了。
1 #include<cstdio> 2 #include<set> 3 #include<iostream> 4 #include<algorithm> 5 #define int long long 6 #define HZOI using namespace std 7 HZOI; 8 const int MAXN=1e6+3; 9 struct node{ 10 int lc,rc,num; 11 }tr[MAXN<<5]; 12 int n,m,Q,cnt; 13 int tt,vv[MAXN<<1],nx[MAXN<<1],first[MAXN]; 14 int qk[MAXN],qnum[MAXN],a[MAXN],ans[MAXN]; 15 int w[MAXN]; 16 int head,tail,q[MAXN],fa[MAXN],vis[MAXN]; 17 int num[MAXN]; 18 set<int> s[MAXN]; 19 void Bfs(int ); 20 void Dfs(int ,int ); 21 void Insert(int &,int ,int ,int ); 22 int Merge(int ,int ); 23 inline void Add(int ,int ); 24 inline int read(); 25 signed main() 26 { 27 scanf("%lld",&n); 28 cnt=n; 29 for (int i=1,x,y; i<n; ++i) 30 { 31 scanf("%lld%lld",&x,&y); 32 Add(x,y); Add(y,x); 33 } 34 if (n<=1000 and m<=1000) 35 { 36 Bfs(1); 37 for (int i=1; i<=n; ++i) 38 scanf("%lld",&w[i]); 39 scanf("%lld",&m); 40 for (int i=1; i<=m; ++i) 41 scanf("%lld%lld",&qk[i],&qnum[i]),a[i]=qnum[i]; 42 sort(a+1,a+1+m); 43 int lena=unique(a+1,a+1+m)-a-1; 44 for (int i=1; i<=m; ++i) 45 qnum[i]=lower_bound(a+1,a+lena+1,qnum[i])-a; 46 for (int i=1; i<=m; ++i) 47 { 48 int to=qk[i],data=qnum[i]; 49 while (to) 50 { 51 if (w[to] and s[to].find(data)!=s[to].end()) --w[to]; 52 if (w[to] and s[to].find(data)==s[to].end()) ++num[to],s[to].insert(data),--w[to]; 53 to=fa[to]; 54 } 55 } 56 scanf("%lld",&Q); 57 for (int i=1,x; i<=Q; ++i) 58 { 59 scanf("%lld",&x); 60 printf("%lld ",num[x]); 61 } 62 } 63 else 64 { 65 for (int i=1; i<=n; ++i) 66 scanf("%lld",&w[i]); 67 scanf("%lld",&m); 68 for (int i=1; i<=m; ++i) 69 scanf("%lld%lld",&qk[i],&qnum[i]),a[i]=qnum[i]; 70 sort(a+1,a+1+m); 71 int lena=unique(a+1,a+1+m)-a-1; 72 for (int i=1; i<=m; ++i) 73 qnum[i]=lower_bound(a+1,a+lena+1,qnum[i])-a; 74 for (int i=1; i<=m; ++i) 75 Insert(qk[i],1,lena,qnum[i]); 76 Dfs(1,0); 77 scanf("%lld",&Q); 78 for (int i=1,x; i<=Q; ++i) 79 { 80 scanf("%lld",&x); 81 printf("%lld ",tr[x].num); 82 } 83 } 84 return 0; 85 } 86 void Insert(int &k,int l,int r,int data) 87 { 88 if (!k) k=++cnt; 89 if (l==r) { tr[k].num=1; return ; } 90 int mid=l+r>>1; 91 if (data<=mid) Insert(tr[k].lc,l,mid,data); 92 else Insert(tr[k].rc,mid+1,r,data); 93 tr[k].num=tr[tr[k].lc].num+tr[tr[k].rc].num; 94 } 95 int Merge(int x,int y) 96 { 97 if (!x or !y) return x+y; 98 tr[x].lc=Merge(tr[x].lc,tr[y].lc); 99 tr[x].rc=Merge(tr[x].rc,tr[y].rc); 100 if (!tr[x].lc and !tr[x].rc) return x; 101 tr[x].num=tr[tr[x].lc].num+tr[tr[x].rc].num; 102 return x; 103 } 104 void Dfs(int k,int father) 105 { 106 for (int i=first[k]; i; i=nx[i]) 107 { 108 if (vv[i]==father) continue; 109 Dfs(vv[i],k); 110 Merge(k,vv[i]); 111 } 112 } 113 void Bfs(int k) 114 { 115 head=tail=0; 116 q[++tail]=k; 117 fa[k]=0; 118 vis[k]=1; 119 while (head^tail) 120 { 121 int x=q[++head]; 122 for (int i=first[x]; i; i=nx[i]) 123 { 124 int ver=vv[i]; 125 if (vis[ver]) continue; 126 fa[ver]=x; 127 q[++tail]=ver; 128 vis[ver]=1; 129 } 130 } 131 } 132 inline void Add(int u,int v) 133 { 134 vv[++tt]=v; nx[tt]=first[u]; first[u]=tt; 135 } 136 inline int read() 137 { 138 int num=0; char ch=getchar(); 139 while (ch<'0' or ch>'9') ch=getchar(); 140 while (ch>='0' and ch<='9') num=(num<<3)+(num<<1)+(ch^48),ch=getchar(); 141 return num; 142 }
这道题好在它的思路不同以往普通线段树,它更新了我对线段树维护信息的认识。
这道题以操作时间为坐标轴,并以之为基础建立线段树,通过操作的不同顺序来维护所需要的信息。
这个思路很棒啊,考试的时候真的想不到。
那么该怎么操作呢?
我们开很多很多个$vector$,来存在某个节点的操作的颜色及时间。
大体思路就是找到最深层的叶子节点,然后逐层向上合并,最后合并至根节点,期间维护各种信息,使答案合法。
先插一下合并方法:线段树合并在这里肯定不好使,它合并的很快,但是只能将两颗线段树上的对应位置合并在一起,而无法判断子树之间的影响,因此它不再适用。所以我们选择一种相对暴力的方法,启发式合并(名字好高端,但其实很暴力),思想就是把大的插入到小的里边,怎么插?取出树中的所有信息,暴力插!
以为要把小的塞进大的里,所以我们要找到每一个节点的最大子树,称它为重儿子,然后进行$DFS$。
这里面,我们显然需要记录某个元素是否在比他更早的时间里出现过,用到了桶,可是桶很难受,它不能开到二维的数据范围$1e5$,所以我们有用到了一个小思想,把桶重复利用,每操作完一个节点就把其中的信息全部清空,注意,这里复杂度可能不好把握,一定不能用$memset$,用多少删多少,这样才可以让时间复杂度有保障。
我们的操作大多进行在$vector$中,统计答案只需要把$vector$中的元素一个个插入线段树中,最后访问,就可以了。
注意这种算法,每一个更新到的字节点最后他们的桶都要清空。
$TLE 30$。
1 #include<cstdio> 2 #include<vector> 3 #include<iostream> 4 #include<algorithm> 5 #define int long long 6 #define HZOI using namespace std 7 HZOI; 8 const int MAXN=5e6+3; 9 struct node{ 10 int lc,rc,num,sum; 11 }tr[MAXN<<3]; 12 int n,m,Q,cnt; 13 int tt,vv[MAXN<<1],nx[MAXN<<1],first[MAXN]; 14 int qk[MAXN],qnum[MAXN],a[MAXN],sze[MAXN],hea[MAXN]; 15 int w[MAXN],ans[MAXN],t[MAXN],root[MAXN]; 16 vector<pair<int ,int > > vec[MAXN]; 17 void Dfs(int ,int ); 18 void Dfss(int ,int ); 19 void Insert(int &,int ,int ,int ,int ,int ); 20 int Ask(int ,int ,int ,int ); 21 void Merge(int ,int ); 22 inline void Add(int ,int ); 23 inline int read(); 24 signed main() 25 { 26 scanf("%lld",&n); 27 for (int i=1,x,y; i<n; ++i) 28 { 29 scanf("%lld%lld",&x,&y); 30 Add(x,y); Add(y,x); 31 } 32 for (int i=1; i<=n; ++i) 33 scanf("%lld",&w[i]); 34 scanf("%lld",&m); 35 for (int i=1; i<=m; ++i) 36 scanf("%lld%lld",&qk[i],&qnum[i]),a[i]=qnum[i]; 37 sort(a+1,a+1+m); 38 int lena=unique(a+1,a+1+m)-a-1; 39 for (int i=1; i<=m; ++i) 40 qnum[i]=lower_bound(a+1,a+lena+1,qnum[i])-a; 41 for (int i=1; i<=m; ++i) 42 vec[qk[i]].push_back(make_pair(i,qnum[i])); 43 Dfs(1,0); 44 Dfss(1,0); 45 scanf("%lld",&Q); 46 for (int i=1; i<=Q; ++i) 47 printf("%lld ",ans[read()]); 48 return 0; 49 } 50 void Dfss(int k,int father) 51 { 52 for (int i=first[k]; i; i=nx[i]) 53 { 54 int ver=vv[i]; 55 if (ver==father or ver==hea[k]) continue; 56 Dfss(ver,k); 57 for (int j=0; j<vec[ver].size(); ++j) 58 t[vec[ver][j].second]=0; 59 } 60 if (hea[k]) 61 { 62 Dfss(hea[k],k); 63 for (int j=0; j<vec[hea[k]].size(); ++j) 64 t[vec[hea[k]][j].second]=0; 65 Merge(hea[k],k); 66 swap(vec[hea[k]],vec[k]); 67 } 68 for (int i=first[k]; i; i=nx[i]) 69 { 70 int ver=vv[i]; 71 if (ver==father or ver==hea[k]) continue; 72 Merge(k,ver); 73 } 74 for (int i=0; i<vec[k].size(); ++i) 75 { 76 int time=vec[k][i].first,color=vec[k][i].second; 77 if (!t[color]) Insert(root[k],1,m,time,1,0),t[color]=time; 78 else if (t[color]>time) 79 { 80 Insert(root[k],1,m,time,1,0); 81 Insert(root[k],1,m,t[color],-1,0); 82 t[color]=time; 83 } 84 Insert(root[k],1,m,time,0,1); 85 } 86 ans[k]=Ask(root[k],1,m,w[k]); 87 } 88 int Ask(int k,int l,int r,int weight) 89 { 90 if (weight<=0) return 0; 91 if (l==r) return tr[k].num; 92 int mid=l+r>>1,res=0; 93 if (tr[tr[k].lc].sum<=weight) 94 { 95 res+=tr[tr[k].lc].num; 96 res+=Ask(tr[k].rc,mid+1,r,weight-tr[tr[k].lc].sum); 97 return res; 98 } 99 else res=Ask(tr[k].lc,l,mid,weight); 100 return res; 101 } 102 void Insert(int &k,int l,int r,int goal,int col,int que) 103 { 104 if (!k) k=++cnt; 105 if (l==r) 106 { 107 tr[k].sum+=que; tr[k].num+=col; 108 return ; 109 } 110 int mid=l+r>>1; 111 if (goal<=mid) Insert(tr[k].lc,l,mid,goal,col,que); 112 else Insert(tr[k].rc,mid+1,r,goal,col,que); 113 tr[k].num=tr[tr[k].lc].num+tr[tr[k].rc].num; 114 tr[k].sum=tr[tr[k].lc].sum+tr[tr[k].rc].sum; 115 } 116 void Merge(int x,int y) 117 { 118 for (int i=0; i<vec[y].size(); ++i) 119 vec[x].push_back(vec[y][i]); 120 vec[y].clear(); 121 } 122 void Dfs(int k,int father) 123 { 124 for (int i=first[k]; i; i=nx[i]) 125 { 126 if (vv[i]==father) continue; 127 Dfs(vv[i],k); 128 if (sze[vv[i]]>sze[hea[k]]) hea[k]=vv[i]; 129 sze[k]+=sze[vv[i]]; 130 } 131 ++sze[k]; 132 } 133 inline void Add(int u,int v) 134 { 135 vv[++tt]=v; nx[tt]=first[u]; first[u]=tt; 136 } 137 inline int read() 138 { 139 int nn=0; int ch=getchar(); 140 while (ch<'0' or ch>'9') ch=getchar(); 141 while (ch>='0' and ch<='9') nn=(nn<<3)+(nn<<1)+(ch^48),ch=getchar(); 142 return nn; 143 }
接下来才是真正的正解。
我们发现把每一个重儿子的桶都清空,太多了,时间根本接受不了,怎么办?
想一下我们为什么要选择重儿子作为基树。
重儿子的子树比较大,这正是我们保证时间复杂度的要求。
那么我们只要将重儿子的信息一层一层往上传上去,将重儿子的信息传到父节点上不就好了么。
因此,只要父节点k有重儿子,我们就把它的$root$置成重儿子的$root$,然后在k这颗树上进行操作,最后将重儿子信息整合到k上,结束了。
注意不要直接将重儿子插到$k$上,要利用$vector$的交换方式。
小弟不才。
1 #include<cstdio> 2 #include<vector> 3 #include<iostream> 4 #include<algorithm> 5 #define int long long 6 #define HZOI using namespace std 7 HZOI; 8 const int MAXN=5e6+3; 9 struct node{ 10 int lc,rc,num,sum; 11 }tr[MAXN<<3]; 12 int n,m,Q,cnt; 13 int tt,vv[MAXN<<1],nx[MAXN<<1],first[MAXN]; 14 int qk[MAXN],qnum[MAXN],a[MAXN],sze[MAXN],hea[MAXN]; 15 int w[MAXN],ans[MAXN],t[MAXN],root[MAXN]; 16 vector<pair<int ,int > > vec[MAXN]; 17 void Dfs(int ,int ); 18 void Dfss(int ,int ); 19 void Insert(int &,int ,int ,int ,int ,int ); 20 int Ask(int ,int ,int ,int ); 21 void Merge(int ,int ); 22 inline void Add(int ,int ); 23 inline int read(); 24 signed main() 25 { 26 scanf("%lld",&n); 27 for (int i=1,x,y; i<n; ++i) 28 { 29 scanf("%lld%lld",&x,&y); 30 Add(x,y); Add(y,x); 31 } 32 for (int i=1; i<=n; ++i) 33 scanf("%lld",&w[i]); 34 scanf("%lld",&m); 35 for (int i=1; i<=m; ++i) 36 scanf("%lld%lld",&qk[i],&qnum[i]),a[i]=qnum[i]; 37 sort(a+1,a+1+m); 38 int lena=unique(a+1,a+1+m)-a-1; 39 for (int i=1; i<=m; ++i) 40 qnum[i]=lower_bound(a+1,a+lena+1,qnum[i])-a; 41 for (int i=1; i<=m; ++i) 42 vec[qk[i]].push_back(make_pair(i,qnum[i])); 43 Dfs(1,0); 44 Dfss(1,0); 45 scanf("%lld",&Q); 46 for (int i=1; i<=Q; ++i) 47 printf("%lld ",ans[read()]); 48 return 0; 49 } 50 void Dfss(int k,int father) 51 { 52 for (int i=first[k]; i; i=nx[i]) 53 { 54 int ver=vv[i]; 55 if (ver==father or ver==hea[k]) continue; 56 Dfss(ver,k); 57 for (int j=0; j<vec[ver].size(); ++j) 58 t[vec[ver][j].second]=0; 59 } 60 if (hea[k]) 61 { 62 Dfss(hea[k],k); 63 root[k]=root[hea[k]]; 64 } 65 for (int i=first[k]; i; i=nx[i]) 66 { 67 int ver=vv[i]; 68 if (ver==father or ver==hea[k]) continue; 69 Merge(k,ver); 70 } 71 for (int i=0; i<vec[k].size(); ++i) 72 { 73 int time=vec[k][i].first,color=vec[k][i].second; 74 if (!t[color]) Insert(root[k],1,m,time,1,0),t[color]=time; 75 else if (t[color]>time) 76 { 77 Insert(root[k],1,m,time,1,0); 78 Insert(root[k],1,m,t[color],-1,0); 79 t[color]=time; 80 } 81 Insert(root[k],1,m,time,0,1); 82 } 83 ans[k]=Ask(root[k],1,m,w[k]); 84 if (hea[k]) 85 { 86 Merge(hea[k],k); 87 swap(vec[k],vec[hea[k]]); 88 } 89 } 90 int Ask(int k,int l,int r,int weight) 91 { 92 if (weight<=0) return 0; 93 if (l==r) return tr[k].num; 94 int mid=l+r>>1,res=0; 95 if (tr[tr[k].lc].sum<=weight) 96 { 97 res+=tr[tr[k].lc].num; 98 res+=Ask(tr[k].rc,mid+1,r,weight-tr[tr[k].lc].sum); 99 return res; 100 } 101 else res=Ask(tr[k].lc,l,mid,weight); 102 return res; 103 } 104 void Insert(int &k,int l,int r,int goal,int col,int que) 105 { 106 if (!k) k=++cnt; 107 if (l==r) 108 { 109 tr[k].sum+=que; tr[k].num+=col; 110 return ; 111 } 112 int mid=l+r>>1; 113 if (goal<=mid) Insert(tr[k].lc,l,mid,goal,col,que); 114 else Insert(tr[k].rc,mid+1,r,goal,col,que); 115 tr[k].num=tr[tr[k].lc].num+tr[tr[k].rc].num; 116 tr[k].sum=tr[tr[k].lc].sum+tr[tr[k].rc].sum; 117 } 118 void Merge(int x,int y) 119 { 120 for (int i=0; i<vec[y].size(); ++i) 121 vec[x].push_back(vec[y][i]); 122 vec[y].clear(); 123 } 124 void Dfs(int k,int father) 125 { 126 for (int i=first[k]; i; i=nx[i]) 127 { 128 if (vv[i]==father) continue; 129 Dfs(vv[i],k); 130 if (sze[vv[i]]>sze[hea[k]]) hea[k]=vv[i]; 131 sze[k]+=sze[vv[i]]; 132 } 133 ++sze[k]; 134 } 135 inline void Add(int u,int v) 136 { 137 vv[++tt]=v; nx[tt]=first[u]; first[u]=tt; 138 } 139 inline int read() 140 { 141 int nn=0; int ch=getchar(); 142 while (ch<'0' or ch>'9') ch=getchar(); 143 while (ch>='0' and ch<='9') nn=(nn<<3)+(nn<<1)+(ch^48),ch=getchar(); 144 return nn; 145 }
T3 大佬(kat)
妈呀概率期望大水题。
我这么想做概率期望,但是一碰到概率期望还是懵。
其实很简单的。
先解释一下为什么我们可以直接乘$n-k+1$次。
我们每一次将方格移动一位,会发现其中有$k-1$位是重复的,那么这重复的部分怎么处理呢?其实不用处理,每次的移动会考虑到所有情况,情况的交集一定存在。
那么找规律吧,这个……口胡好像不太好讲,就是每一位上不重不漏的算出所有情况,总情况是$m^k$。
公式
$ans=(n-k+1) imes sum limits _{i=1}^m sum limits _{j=1}^k {(i-1)}^{j-1} i^{k-j} w_i inv_{m^k}$
小弟不才。
1 #include<cstdio> 2 #define int long long 3 #define HZOI using namespace std 4 HZOI; 5 const int mod=1e9+7; 6 int n,m,k,invtot,ans; 7 inline int Pow(int ,int ); 8 signed main() 9 { 10 scanf("%lld%lld%lld",&n,&m,&k); 11 if (k>n) {puts("0"); return 0;} 12 invtot=Pow(Pow(m,k),mod-2); 13 for (int i=1,w; i<=m; ++i) 14 { 15 scanf("%lld",&w); 16 for (int j=1; j<=k; ++j) 17 ans=(ans+Pow(i-1,j-1)%mod*Pow(i,k-j)%mod*w%mod*invtot%mod+mod)%mod; 18 } 19 ans=(ans%mod*(n-k+1)+mod)%mod; 20 printf("%lld ",(ans+mod)%mod); 21 return 0; 22 } 23 inline int Pow(int x,int y) 24 { 25 int res=1; 26 while (y) 27 { 28 if (y&1) res=res%mod*x%mod; 29 x=x%mod*x%mod; 30 y>>=1; 31 } 32 return res; 33 }