CSUST选拔赛题解
---恢复内容开始---
A:哭泣的阿木木
题目链接:http://csustacm.com:4803/contest/26/problem/J
思路:这是一个很裸的线段树+lazy数组标记,直接用板子就可以了,数据很大很多,所以开long long,并且用scanf,printf输入输出。
代码如下:
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cmath> 5 using namespace std; 6 long long a[1000010]; 7 long long tree[4000010]; 8 long long lazy[4000010]; 9 void pushup(int num) { 10 tree[num]=tree[num<<1]+tree[num<<1|1]; 11 return ; 12 } 13 14 void pushdown(int l,int r,int num) { 15 int mid=(l+r)>>1; 16 if(lazy[num]) { 17 tree[num<<1] += (lazy[num]*(mid-l+1)); 18 tree[num<<1|1] += (lazy[num]*(r-mid)); 19 lazy[num<<1] +=lazy[num]; 20 lazy[num<<1|1] +=lazy[num]; 21 } 22 lazy[num] = 0 ; 23 return ; 24 } 25 26 void build(int l,int r,int num) { 27 if(l==r) { 28 tree[num]=a[l]; 29 return ; 30 } 31 int mid = (l+r) >>1; 32 build(l,mid,num<<1); 33 build(mid+1,r,num<<1|1); 34 pushup(num); 35 } 36 37 void quriy(int l,int r,int le,int ri,int num,long long& ans) { 38 if(l>ri||r<le) return ; 39 if(le<=l&&r<=ri) { 40 ans+=tree[num]; 41 return ; 42 } 43 pushdown(l,r,num); 44 int mid = (l+r)>>1; 45 if(mid>=le) quriy(l,mid,le,ri,num<<1,ans); 46 if(mid<ri) quriy(mid+1,r,le,ri,num<<1|1,ans); 47 pushup(num); 48 return ; 49 } 50 51 void modify(int l,int r,int le,int ri,int num,long long mods) { 52 if(l>ri||r<le) return ; 53 if(le<=l&&r<=ri) { 54 lazy[num]+=mods; 55 tree[num] += mods*(r-l+1); 56 return ; 57 } 58 pushdown(l,r,num); 59 int mid = (l+r) >>1; 60 if(mid>=le) modify(l,mid,le,ri,num<<1,mods); 61 if(mid<ri) modify(mid+1,r,le,ri,num<<1|1,mods); 62 pushup(num); 63 return ; 64 } 65 66 int main() { 67 int n,q; 68 scanf("%d%d",&n,&q); 69 for(int i=1;i<=n;i++) { 70 scanf("%lld",&a[i]); 71 } 72 build(1,n,1); 73 char ss[10]; 74 int cmd; 75 int le,ri; 76 long long k; 77 while(q--) { 78 scanf("%1s%d",ss,&cmd); 79 if(cmd==1) { 80 scanf("%d%d%lld",&le,&ri,&k); 81 modify(1,n,le,ri,1,k); 82 } 83 else if(cmd==2) { 84 scanf("%d%d%lld",&le,&ri,&k); 85 modify(1,n,le,ri,1,-k); 86 } 87 else if(cmd==3) { 88 scanf("%d%lld",&le,&k); 89 ri=le; 90 long long ans=0; 91 quriy(1,n,le,le,1,ans); 92 modify(1,n,le,le,1,k-ans); 93 } 94 else { 95 scanf("%d%d",&le,&ri); 96 long long ans=0; 97 quriy(1,n,le,ri,1,ans); 98 cout<<ans<<endl; 99 } 100 } 101 return 0; 102 }
B:点进来吧,这里有你想要的
题目链接:http://10.64.70.166/contest/27/problem/I
思路:可以看成只有两个物品的背包问题,用DP来解决,dp动态移动方程为:dp[i]=dp[i-1]+dp[i-m] . 表示高兴度为 i 所拥有的方案数为高兴度为 i-1 的方案数(即再吃一个小食) 加上高兴度为 i-m 的方案数(即再吃一个汉堡).
需要注意的是因为吃小食和吃汉堡的顺序不同会被算为不同的方案数,所以要是先枚举物品,再枚举高兴度会少算。
代码如下:
1 #include <iostream> 2 #include <algorithm> 3 using namespace std; 4 5 long long dp[200050]; 6 const long long mod = 1e8+7; 7 8 int main() { 9 int t; 10 cin>>t; 11 while(t--) { 12 int n,m; 13 cin>>n>>m; 14 for(int i=0;i<=n;i++) dp[i]=0; 15 dp[0]=1; 16 for(int i=1;i<=n;i++) {//先枚举高兴度 17 if(i>=1) {//如果能吃小食就加上吃小食之前的方案数. 18 dp[i] = (dp[i]%mod+dp[i-1]%mod)%mod; 19 } 20 if(i>=m) {//同上 21 dp[i] = (dp[i]%mod+dp[i-m]%mod)%mod; 22 } 23 } 24 cout<<dp[n]<<endl; 25 } 26 return 0; 27 }
C:寻宝
题目链接:http://10.64.70.166/contest/27/problem/E
思路:读完题可能感觉是道比较难的题,我们可以先考虑只有两个人寻宝的所有情况(即每种宝藏都只有两个).如图下这种情况下,我们可以知道a1走向b2,a2走向b2的方法是最优的(其他走法会多走a2-a1之间的距离),其他情况大家可以
例举,可以得出最小的走最小一定是最短的走法. 那这样这道题就是一道很明显的贪心题,数据范围也不大,只要注意求绝对值和开long long即可。
代码如下:
1 #include <iostream> 2 #include <algorithm> 3 #include <cmath> 4 using namespace std; 5 long long a[110][10]; 6 7 8 int main() { 9 int n,k; 10 cin>>n>>k; 11 for(int i=0;i<n;i++) { 12 for(int j=0;j<k;j++) { 13 cin>>a[i][j]; 14 } 15 sort(a[i],a[i]+k);//讲每种宝藏从小到大排序. 16 } 17 long long ans=0; 18 for(int j=0;j<k;j++) { 19 for(int i=1;i<n;i++) { 20 ans += abs(a[i][j]-a[i-1][j]);//最小的走最小的. 21 } 22 } 23 cout<<ans<<endl; 24 return 0; 25 }
D:武器大师的宝贝
题目链接:http://10.64.70.166/contest/27/problem/D
思路:如果了解位运算符 ^ 的话这道题很显然是求两个区间的相重合的部分,代码很简单但是细节很多,比如要输出最简形式,所以我们要用欧几里得算法求分子和分母的最大公约数。
代码如下:
1 #include <iostream> 2 #include <algorithm> 3 using namespace std; 4 5 long long gcd(long long a,long long b) { 6 return b==0? a:gcd(b,a%b); 7 } 8 9 int main() { 10 int t; 11 cin>>t; 12 while(t--) { 13 long long a,b,c,d; 14 cin>>a>>b>>c>>d; 15 if(a>c) { 16 swap(a,c); 17 swap(b,d); 18 } 19 if(b<c) cout<<"0/1"<<endl; //判断两个区间有没有交集. 20 else { 21 long long di = (b-a+1)*(d-c+1); 22 long long ss = min(b,d)-max(a,c)+1; 23 cout<<ss/gcd(ss,di)<<'/'<<di/gcd(ss,di)<<endl; 24 } 25 } 26 return 0; 27 }
E:千万别点进来,点进来你就哭了
题目链接:http://10.64.70.166/contest/27/problem/F
思路:n次求最短路,到所有点和最小的那次最短路就是我们要求的结果,最短路用disjike的要用优先队列优化,否则会tle。
代码如下:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <queue> 5 #define inf 1e18 6 using namespace std; 7 priority_queue<struct node> q; 8 long long d[1100]; 9 long long ans; 10 int ans_num; 11 int vis[1100]; 12 int n,m; 13 struct Next { 14 int tos;long long val; 15 16 Next(int tos=0,long long val=0) { 17 this->tos = tos; 18 this->val = val; 19 } 20 }; 21 struct Next s; 22 vector<Next> to[1100]; 23 24 struct node { 25 int num; 26 long long dis; 27 bool operator < (const node& a) const { 28 return a.dis<=this->dis; 29 } 30 node(int num=0,long long dis = 0) { 31 this->num = num; 32 this->dis = dis; 33 } 34 }; 35 36 int disjike(int st) { 37 while(!q.empty()) q.pop(); 38 for(int i=1;i<=n;i++) { 39 d[i]=inf; 40 vis[i]=0; 41 } 42 d[st]=0; 43 vis[st]=1; 44 int nums=to[st].size(); 45 for(int i=0;i<nums;i++) { 46 d[to[st][i].tos] = to[st][i].val; 47 struct node nodes = node(to[st][i].tos,d[to[st][i].tos]); 48 q.push(nodes); 49 } 50 for(int j=0;;j++) { 51 if(q.empty()) break; 52 struct node x=q.top(); 53 q.pop(); 54 int nums=to[x.num].size(); 55 if(vis[x.num]==1) continue; 56 if(d[x.num]==inf) continue; 57 vis[x.num]=1; 58 for(int i=0;i<nums;i++) { 59 if(d[to[x.num][i].tos] > d[x.num]+to[x.num][i].val) { 60 d[to[x.num][i].tos]=d[x.num]+to[x.num][i].val; 61 q.push(node(to[x.num][i].tos,d[to[x.num][i].tos])); 62 } 63 } 64 } 65 long long sum=0; 66 for(int i=1;i<=n;i++) { 67 sum+=d[i]; 68 } 69 if(sum<=ans) { 70 ans=sum; 71 ans_num=st; 72 } 73 return 0; 74 } 75 76 77 int main() { 78 while(cin>>n>>m) { 79 ans=inf; 80 for(int i=1;i<=n;i++) to[i].clear(); 81 for(int i=0;i<m;i++) { 82 int a,b;long long c; 83 cin>>a>>b>>c; 84 s=Next(a,c); 85 to[b].push_back(s); 86 s.tos=b; 87 to[a].push_back(s); 88 } 89 for(int i=n;i>=1;i--) { 90 disjike(i); 91 } 92 cout<<ans<<' '<<ans_num<<endl; 93 } 94 return 0; 95 }
F:提莫队长正在待命!
题目链接:http://10.64.70.166/contest/27/problem/B
思路:可以利用并查集解决,没有蘑菇的话就合并,有蘑菇就不合并,最后会得到几个集合,A集合的点走到B集合的点中时一定会经过他们之间那条有蘑菇的路(路径数就等于 A.size()*B.size() ),要求的结果就是所有集合走到其他集合所需路径数之和。
代码如下:
1 #include <iostream> 2 #include <algorithm> 3 using namespace std; 4 int f[300050]; 5 long long q[300050]={0}; 6 int finds(int x) { 7 return f[x]==x? x:f[x]=finds(f[x]); 8 } 9 10 int unions(int x,int y) { 11 x=finds(x); 12 y=finds(y); 13 if(x==y) return 0; 14 else { 15 f[x]=y; 16 } 17 return 0; 18 } 19 20 21 int main() { 22 int n; 23 cin>>n; 24 for(int i=1;i<=n;i++) f[i]=i; 25 for(int i=0;i<n-1;i++) { 26 int a,b,c; 27 cin>>a>>b>>c; 28 if(c==1) continue; 29 else { 30 unions(a,b); 31 } 32 } 33 long long ans=0; 34 for(int i=1;i<=n;i++) { 35 q[finds(i)]++; 36 } 37 for(int i=1;i<=n;i++) { 38 if(q[i]) ans += (q[i]*(n-q[i])); 39 } 40 cout<<ans<<endl; 41 return 0; 42 }
G:凛冬将至
题目链接:http://10.64.70.166/contest/27/problem/C
思路:LCA板子+枚举所有情况,即不使用密道的距离,使用密道(两种,起点->u->v->终点或起点->v->u->终点)的距离.
代码如下:
1 #include <iostream> 2 #include <vector> 3 #include <cmath> 4 #define maxn 100009 5 using namespace std; 6 int n,N; 7 8 struct edge{ 9 int from,to,w; 10 }; 11 edge edges[maxn<<1]; 12 vector<int> G[maxn]; 13 14 int dp[maxn][100]={0},gw[maxn][100]={0}; 15 int depth[maxn]; 16 17 void dfs(int x) { 18 for(int i=1;i<=N;i++) { 19 dp[x][i]=dp[dp[x][i-1]][i-1]; 20 gw[x][i]=gw[x][i-1]+gw[dp[x][i-1]][i-1]; 21 } 22 for(int i=0;i<G[x].size();i++) { 23 edge e = edges[G[x][i]]; 24 depth[e.to]=depth[x]+1; 25 dp[e.to][0]=x; 26 gw[e.to][0]=e.w; 27 dfs(e.to); 28 } 29 } 30 } 31 32 int lca(int a,int b) { 33 if(a==b) return 0; 34 if(depth[a]>depth[b]) swap(a,b); 35 int ans=0; 36 for(int i = N;i>=0;i--) { 37 if(depth[a] < depth[b] && depth[dp[b][i]] >= depth[a]) ans+=gw[b][i],b=dp[b][i]; 38 } 39 for(int j=N;j>=0;j--) { 40 if(dp[a][j]!=dp[b][j]) { 41 ans+=gw[a][j]; 42 ans+=gw[b][j]; 43 a=dp[a][j]; 44 b=dp[b][j]; 45 } 46 } 47 if(a!=b) { 48 ans+=gw[a][0],ans+=gw[b][0]; 49 } 50 return ans; 51 } 52 53 int main() { 54 cin>>n; 55 for(int i=0;i<2*(n-1);i+=2) { 56 int a,b,c; 57 cin>>a>>b>>c; 58 edges[i].from=a; 59 edges[i].to=b; 60 edges[i].w=c; 61 edges[i+1].from=b; 62 edges[i+1].to=a; 63 edges[i+1].w=c; 64 G[a].push_back(i); 65 G[b].push_back(i+1); 66 } 67 N = log(n+0.0)/log(2.0); 68 depth[1]=0; 69 dfs(1); 70 int le,ri,val; 71 cin>>le>>ri>>val; 72 int q; 73 cin>>q; 74 while(q--) { 75 int as,bs; 76 cin>>as>>bs; 77 int ans=lca(as,bs); 78 ans=min(ans,min(lca(as,le)+val+lca(ri,bs),lca(bs,le)+val+lca(ri,as))); 79 cout<<ans<<endl; 80 } 81 return 0; 82 }
H:小明买年糕
题目链接:http://10.64.70.166/contest/27/problem/H
思路:二分W,因为可能区间数较多,所以我们可以用前缀和优化,这样就能复杂度o(n+q)情况下完成一次判断.
代码如下:
1 #include <iostream> 2 #include <cmath> 3 #define inf 1e18 4 using namespace std; 5 int n,m; 6 long long s; 7 long long w[200010],v[200010]; 8 long long qu[200010][2]; 9 long long sum[200010]; 10 long long num[200010]; 11 long long ans=inf; 12 13 long long is_ok(long long x) { 14 long long anss=0; 15 sum[0]=0; 16 num[0]=0; 17 for(int i=1;i<=n;i++) { 18 if(w[i]>=x) { 19 sum[i]=sum[i-1]+v[i]; 20 num[i]=num[i-1]+1; 21 } 22 else { 23 sum[i]=sum[i-1]; 24 num[i]=num[i-1]; 25 } 26 } 27 28 for(int i=0;i<m;i++) { 29 30 anss+=(sum[qu[i][1]]-sum[qu[i][0]-1])*(num[qu[i][1]]-num[qu[i][0]-1]); 31 32 } 33 if(abs(anss-s)<ans) { 34 ans=abs(anss-s); 35 } 36 return anss; 37 } 38 39 int main() { 40 41 long long l=0,r=0; 42 cin>>n>>m>>s; 43 for(int i=1;i<=n;i++) { 44 cin>>w[i]>>v[i]; 45 r=max(w[i],r); 46 } 47 for(int i=0;i<m;i++) { 48 cin>>qu[i][0]>>qu[i][1]; 49 } 50 while(l<=r) { 51 long long mid =(l+r) >> 1; 52 if(is_ok(mid)<s) { 53 r=mid-1; 54 } 55 else { 56 l=mid+1; 57 } 58 } 59 is_ok(l-1); 60 is_ok(l+1); 61 cout<<ans<<endl; 62 return 0; 63 }
I:可怜的ljb
题目链接:http://10.64.70.166/contest/27/problem/G
思路:线段树/树状数组求逆序对
代码如下:
1 #include <iostream> 2 3 using namespace std; 4 int c[100010]; 5 int n; 6 int a[100010]; 7 void init(int n) { 8 for(int i=1;i<=n;i++) c[i]=0; 9 } 10 11 int lowbit(int x) { 12 return x&-x; 13 } 14 15 void update(int pos,int d) { 16 while(pos<=n) { 17 c[pos]+=d; 18 pos += lowbit(pos); 19 } 20 return ; 21 } 22 23 int getsum(int x) { 24 int sum=0; 25 while(x>0) { 26 sum+=c[x]; 27 x-=lowbit(x); 28 } 29 return sum; 30 } 31 32 int main() { 33 int t; 34 cin>>t; 35 while(t--) { 36 long long ans=0; 37 cin>>n; 38 init(n); 39 for(int i=1;i<=n;i++) { 40 int s; 41 cin>>s; 42 a[s]=i; 43 } 44 for(int i=1;i<=n;i++) { 45 int s; 46 cin>>s; 47 ans+= (long long )getsum(n)-getsum(a[s]); 48 update(a[s],1); 49 } 50 cout<<ans<<endl; 51 } 52 return 0; 53 }
J:“一个部族,一个民族,一个弗雷尔卓德。”
题目链接:http://10.64.70.166/contest/27/problem/A
思路:线性筛+树状数组,题目要求的求所有小于数n的并且最小素数小于或等于n的最小素数的数的总数,我们在利用线性筛筛到数n时,最小素数小于或等于n的最小素数的数一定是被筛完了的,所以我们只要判断小于n的数已经筛过多少就可以了.
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #define maxn 1000000 4 using namespace std; 5 int c[1000010]={0}; 6 int vis[1000010]={0}; 7 int ans[1000010]={0}; 8 9 int lowbit(int x) { 10 return x&-x; 11 } 12 13 void update(int pos,int d) { 14 while(pos<=maxn) { 15 c[pos]+=d; 16 pos+=lowbit(pos); 17 } 18 return ; 19 } 20 21 int getsum(int pos) { 22 int ans=0; 23 while(pos>0) { 24 ans+=c[pos]; 25 pos-=lowbit(pos); 26 } 27 return ans; 28 } 29 30 31 32 int main() { 33 update(1,1); 34 for(int i=2;i<=maxn;i++) { 35 if(vis[i]!=0) continue; 36 int k=i; 37 while(k<=maxn) { 38 if(vis[k]!=0) { 39 k+=i; 40 continue; 41 } 42 ans[k]=getsum(k); 43 vis[k]=1; 44 update(k,1); 45 k+=i; 46 } 47 } 48 int t; 49 scanf("%d",&t); 50 while(t--) { 51 int n; 52 scanf("%d",&n); 53 printf("%d\n",ans[n]); 54 } 55 return 0; 56 }