暑期集训第十五天(7-6)题解及总结
小总结:
今天上午又轮到老姚出题了,老姚这次出题莫名多了一道数论(说好的不出数论呢???),还有就是一如既往的dp多,四道题里面有两道dp题目......
明天高三学长要高考了,祝愿他们能取得的一个好成绩吧(稍微带一下我呗~)
T1:精灵魔法
看到这一道题乍一看是没有什么思路的,但是详细一想,我们可以对编号进行离散化,记录一下一个人前面还有多少人编号比他大,之后以速度为基准进行排序,再数出一个点前面有多少人编号比他大,减一下就好了,原因是两个点的互换在一个点超过一个点和一个点被超过是等价的,我们只统计其中一种就可以了,这里统计的是一个点超过的点的个数,于是我们考虑从大到小枚举,把前面的点记录一下他比几个点大,之后轮到某个点之后看一下前面有几个点比他大,这样就可以统计答案了,这是什么,是区间修改,单点查询,于是选择用线段树进行维护,注意在对速度进行排序的时候要特别处理一下相等的情况,不然会100->70.
其实关于这道题的主流做法是单点修改,区间查询(和我正好相反),还可以用归并排序求逆序对,我都试着打了一下,这里就不放代码了,退一下老师的博客:https://www.cnblogs.com/hbhszxyb/p/13253975.html
1 #include<bits/stdc++.h> 2 #define int long long 3 #define debug printf("-debug-\n") 4 #define lson (t<<1) 5 #define rson (t<<1|1) 6 #define mid ((l+r)>>1) 7 using namespace std; 8 const int N=1e6+10; 9 struct Tree{ 10 int w,lazy,siz; 11 }tree[N]; 12 void pushup(int t){ 13 tree[t].w=tree[lson].w+tree[rson].w; 14 } 15 void build(int t,int l,int r){ 16 tree[t].siz=r-l+1; 17 if(l==r) return; 18 build(lson,l,mid);build(rson,mid+1,r); 19 pushup(t); 20 } 21 void pushdown(int t){ 22 tree[lson].w+=tree[t].lazy; 23 tree[lson].lazy+=tree[t].lazy; 24 tree[rson].w+=tree[t].lazy; 25 tree[rson].lazy+=tree[t].lazy; 26 tree[t].lazy=0; 27 } 28 void change(int t,int l,int r,int cl,int cr){ 29 if(cl<=l&&r<=cr){ 30 tree[t].w+=tree[t].siz; 31 tree[t].lazy++; 32 return; 33 } 34 pushdown(t); 35 if(cr<=mid) change(lson,l,mid,cl,cr); 36 else if(cl>mid) change(rson,mid+1,r,cl,cr); 37 else{ 38 change(lson,l,mid,cl,cr);change(rson,mid+1,r,cl,cr); 39 } 40 pushup(t); 41 } 42 int query(int t,int l,int r,int pos){ 43 if(l==r){ 44 return tree[t].w; 45 } 46 pushdown(t); 47 if(pos<=mid) return query(lson,l,mid,pos); 48 else return query(rson,mid+1,r,pos); 49 } 50 int ans=0,cnt=0; 51 struct Node{ 52 int num,v; 53 }a[N]; 54 bool cmp1(Node a,Node b){ 55 return a.num<b.num; 56 } 57 bool cmp2(Node a,Node b){ 58 if(a.v==b.v) return a.num<b.num; 59 return a.v<b.v; 60 } 61 signed main(){ 62 int n; 63 scanf("%lld",&n); 64 build(1,1,n); 65 for(int i=1;i<=n;++i){ 66 scanf("%lld",&a[i].num); 67 } 68 for(int i=1;i<=n;++i) scanf("%lld",&a[i].v); 69 sort(a+1,a+n+1,cmp1); 70 for(int i=1;i<=n;++i) a[i].num=++cnt;//离散化 71 sort(a+1,a+n+1,cmp2); 72 for(int i=n;i>=1;--i){ 73 int cnt1=n-a[i].num; 74 int cnt2=query(1,1,n,a[i].num); 75 if(cnt1-cnt2>0) ans+=cnt1-cnt2; 76 if(a[i].num>1) change(1,1,n,1,a[i].num-1); 77 } 78 printf("%lld\n",ans); 79 return 0; 80 }
T2:最小环
这道题的题目是误导人的......我们从数据范围之中可以得到n^3的最小环求发是一定不行的,并且你求出的最小环不一定包含一号节点。
这道题目考虑我们如果想要用最短路来跑,要避免的情况就是从1节点出去的方向再次回到1号节点,于是我们可以考虑把从1号节点出去的方向的路线断掉,之后再从被隔离的节点跑一遍最短路,对1号节点此时的距离进行取最小值就可以了。
这到题奇葩的dijksta会T掉,Spfa反而可以,但是我考试的时候吸氧水过了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10; 4 struct Node{ 5 int next,to,dis; 6 }edge[N]; 7 int Head[N],tot=1; 8 void Add(int x,int y,int z){ 9 edge[++tot].to=y; 10 edge[tot].dis=z; 11 edge[tot].next=Head[x]; 12 Head[x]=tot; 13 } 14 struct Edge{ 15 int num,dis; 16 Edge(int x,int y){ 17 num=x;dis=y; 18 } 19 bool operator < (const Edge &a)const{ 20 return a.dis<dis; 21 } 22 }; 23 priority_queue<Edge>q; 24 int dis[N],vis[N],ans=0x3f3f3f3f; 25 void dijkstra(int x){ 26 memset(dis,0x3f,sizeof(dis)); 27 memset(vis,0,sizeof(vis)); 28 dis[x]=0;q.push(Edge(x,0)); 29 while(!q.empty()){ 30 int u=q.top().num;q.pop(); 31 if(vis[u]) continue; 32 vis[u]=1; 33 for(int i=Head[u];i;i=edge[i].next){ 34 int v=edge[i].to; 35 if(dis[v]>dis[u]+edge[i].dis){ 36 dis[v]=dis[u]+edge[i].dis; 37 q.push(Edge(v,dis[v])); 38 } 39 } 40 } 41 } 42 void Init(){ 43 memset(edge,0,sizeof(edge)); 44 memset(Head,0,sizeof(Head)); 45 tot=1;ans=0x3f3f3f3f; 46 } 47 void Solve(){ 48 Init(); 49 int n,m; 50 scanf("%d%d",&n,&m); 51 for(int i=1;i<=m;++i){ 52 int x,y,z; 53 scanf("%d%d%d",&x,&y,&z); 54 Add(x,y,z);Add(y,x,z); 55 } 56 for(int i=Head[1];i;i=edge[i].next){ 57 int v=edge[i].to; 58 int cnt=edge[i].dis; 59 //printf("%d %d %d\n",v,edge[i].dis,edge[i^1].dis); 60 edge[i^1].dis=0x3f3f3f3f; 61 dijkstra(v); 62 ans=min(ans,dis[1]+edge[i].dis); 63 edge[i^1].dis=cnt; 64 } 65 if(ans!=0x3f3f3f3f) printf("%d\n",ans); 66 else printf("-1\n"); 67 } 68 int main(){ 69 int T; 70 scanf("%d",&T); 71 while(T--) Solve(); 72 return 0; 73 }
T3:LGTB 与序列
这道题一看是数论当时直接就放弃去看第四道题了,其实这道题和数论的关系并没有那么大,主要还是一道dp题目。
我们可以考虑ai<=30,其实如果我们去的数字大于58了,就不如直接放1,反正1和任何数字互质,放多少个1都可以,另外,在2~58之内一共有16个质数,于是我们可以吧1~58里面的所有数字进行质因子分解,之后我们可以借助状压dp的思路来记录那些质因子已经被选过了,对于输入的数字我们进行降序排序,用16个质因子里面组成尽可能大的数字来应对a数组之中大的数字,之后就是一道很普通的状压dp题目了,和排列prem就没有什么不同了.
1 #include <bits/stdc++.h> 2 const int maxn=105,maxp=17,Inf=0x3f3f3f3f; 3 int prime[] = {0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}; 4 int n,a[maxn]; 5 int dp[maxp][1<<maxp]; 6 int _prime[maxn]; 7 bool cmp(int a,int b){ 8 return a>b; 9 } 10 int main(){ 11 scanf("%d",&n); 12 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 13 std::sort(a+1,a+n+1,cmp); 14 memset(_prime,0,sizeof(_prime)); 15 for(int i=1;i<=58;i++) 16 for(int j=1;j<=16;j++) 17 if(i<prime[j]) break; 18 else if(i%prime[j]==0) 19 _prime[i]|=(1<<j-1); 20 memset(dp,0x3f,sizeof(dp)); 21 dp[0][0]=0; 22 for(int i=1;i<=std::min(n,16);i++) 23 for(int j=0;j<(1<<16);j++) 24 for(int k=1;k<=58;k++) 25 if(!(_prime[k]&j)){ 26 int s=j|_prime[k]; 27 dp[i][s]=std::min(dp[i][s],dp[i-1][j]+abs(a[i]-k)); 28 } 29 int ans=Inf; 30 for(int j=0;j<(1<<16);j++) 31 ans=std::min(ans,dp[std::min(n,16)][j]); 32 if(n>16) 33 for(int i=17;i<=n;i++) 34 ans+=abs(a[i]-1); 35 printf("%d\n",ans); 36 return 0; 37 }
T4:步步为零
这道题一看就能看出是一道dp的题目,因为它的状态和转移十分明显,但是着手去写的时候你会发现总体思路越写越乱,于是最后我只打了一个爆搜水了20pts......
这道题的dp数组与众不同的是它的数组是bool型的,dp[i][j][k]表示从起点走到(i,j)时得到的数字能否组成k这个数字
到时间了....明天要高考,我们要提前回去,未完待续中........