emmm,你们的学长简直是牲口。。。本来你们都要爆零的, 后面zhrt觉得太不友好了,于是加了两题签到题。
为了便于大家理解,有些题我会尝试一些乱七八糟的解法(可能会A,可能不会),可能会比较眼花缭乱,也不需要大家都掌握,不过某个出题人解法就一定要会了。
题目链接:http://acm.csust.edu.cn/contest/70
比赛过后交不了题,可在problem中搜索
题目说明:
不许“冒泡”(签到题-冒泡排序)
小K的漂流计划(签到题-贪心)
RWBY(最短路)
辛勤的蚂蚁(LCA)
神奇的中位数(优先队列)
不许“冒泡”
题目大意:给你一个序列,让你通过相邻交换使得该序列有序,问最少需要交换多少次并乘以序列长度
Sample Input
3 5 166 167 168 169 170 5 165 166 169 167 168 3 166 167 166
Sample Output
0 10 3
emmm,数据非常小,直接暴力搞就可以了,只不过注意只要序列有序就行了,所以我们要按照升序跑一次冒泡,按照降序跑一次冒泡然后取最小值就OK了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e3+10; int a[mac],b[mac]; int main() { int t,n; scanf ("%d",&t); while (t--){ scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf ("%d",&a[i]); b[i]=a[i]; } int ans=0,ans1=0; for (int i=1; i<=n; i++) for (int j=i+1; j<=n; j++){ if (a[j]>a[j-1]) ans++,swap(a[j],a[j-1]); if (b[j]<b[j-1]) ans1++,swap(b[j],b[j-1]); } printf("%d ",min(ans,ans1)*n); } return 0; }
小K的漂流计划
Sample Input
5 0 5 5 5 5 5
Sample Output
0
#include <bits/stdc++.h> using namespace std; const int mac=1e3+10; int a[mac]; int main() { int n,k; scanf ("%d%d",&n,&k); for (int i=1; i<=n; i++) scanf ("%d",&a[i]); sort(a+1,a+1+n); int ans=1; for (int i=1; i<=n; i++) for (int j=n; j>=i; j--) if (a[j]-a[i]<=k) { ans=max(ans,j-i+1); break; } printf("%d ",n-ans ); return 0; }
接下来就是$logn$的解法,实际上除了排序是$logn$,真正算的时候只有$O(n)$,我们用$head$和$tail$两个指针,如果$a[tail]-a[head]<=k$的话$tail++$,否则的话就$head++$,然后每次保存最大值就OK了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e3+10; int a[mac]; int main() { int n,k; scanf ("%d%d",&n,&k); for (int i=1; i<=n; i++) scanf ("%d",&a[i]); sort(a+1,a+1+n); int head=1,tail=1; int ans=1; while (tail<=n){ while (a[tail]-a[head]<=k && tail<=n) tail++; if (a[tail]-a[head]<=k && tail<=n) ans=max(ans,tail-head+1); else ans=max(ans,tail-head); head++; } printf("%d ",n-ans); return 0; }
RWBY
题目大意:一个图,你要从1走到n,你有k颗尘晶,在整个过程中你的尘晶不能低于0,每个点都有危险值,从危险值a到危险值b的地方(a>b)获得a-b颗尘晶,否则失去b-a颗尘晶。你也可以花费$x^{2}$个金币将某地的危险值下降x。问你最终的最小花费是多少,即金币数+路径长度。
Sample Input
4 5 1 1 2 3 4 1 2 1 1 3 1 1 4 100 2 4 1 3 4 1
Sample Output
6
emmm,应该很容易看出是用最短路求解,但比较头痛的是应对各种条件,我们可以先把尘晶这个变量给剔除掉,你有k颗尘晶,那么你先可以走到刚好满足$a[1]+k$的地方(通过氪金你一定能走到),然后通过金币将其每个点下降至$a[1]+k$那么你后面的路就不需要花费尘晶了,而这样想来花费也是最少的。那么我们就将每个点需要用到的金币同路径长度相加起来,这样就是一条路径的花费了,于是我们就可以愉快地跑一跑最短路就完事了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=2e5+10; typedef long long ll; #define debug printf ("++ ++") struct node{ int to,next; ll w; }eg[mac<<1]; int head[mac],val[mac],num=0; ll dis[mac],cost[mac]; bool vis[mac]; struct pt{ int id; ll s; bool operator<(const pt &a)const{ return s>a.s; } }; void add(int u,int v,ll w) { eg[num]=node{v,head[u],w}; head[u]=num++; } void dij(int st,int n) { priority_queue<pt>q; memset(dis,0x3f,sizeof dis); q.push(pt{st,0}); dis[st]=0; while (!q.empty()){ pt now=q.top(); q.pop(); int u=now.id; if (vis[u]) continue; vis[u]=true; for (int i=head[u]; i!=-1; i=eg[i].next){ int v=eg[i].to; if (vis[v]) continue; if (dis[u]+eg[i].w<dis[v]){ dis[v]=dis[u]+eg[i].w; q.push(pt{v,dis[v]}); } } } } int main() { int n,m,k; memset(head,-1,sizeof head); scanf ("%d%d%d",&n,&m,&k); for (int i=1; i<=n; i++){ scanf("%lld",&val[i]); if (val[i]>val[1]+k){ cost[i]=val[i]-(val[1]+k); cost[i]=cost[i]*cost[i]; } } for (int i=1; i<=m; i++){ int u,v,w; scanf ("%d%d%d",&u,&v,&w); add(u,v,1LL*w+cost[v]);add(v,u,1LL*w+cost[u]); } dij(1,n); printf("%lld ",dis[n]); return 0; }
辛勤的蚂蚁
题目大意:给你一棵树n个点,m只蚂蚁每只要从$s_{i}->t_{i}$,问第k秒时,每个点的蚂蚁数量,到达终点时不再移动。
Sample Input
5 2 3 1 2 1 3 2 4 4 5 2 5 3 5
Sample Output
0 0 0 1 1
emmm,首先看到题目的时候确实有点蒙的,不过一会儿之后就应该能缓解了,一颗树,那么有关的路径算法我们不难想到LCA,而也正是只有LCA才能快速地求出大量s到t的距离,如果时间k大于等于lca(s,t),那没什么好说的了,直接ans[t]++。比较麻烦的是小于的时候,这个时候我们需要判断s的k步能走到哪里,很显然,倍增lca可以通过一步步往上爬祖先节点来求出时间k所达到的点。关键是理解倍增lca是怎么求的,然后这题就很简单了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define debug printf("++ ++ ") const int mac=3e5+10; struct node { int to, next, cost; }eg[mac << 1]; int n, m, tot = 0; int lg[mac], dis[mac], head[mac], depth[mac], father[mac][25]; int ans[mac]; void add(int u, int v, int w) { eg[tot] = node{ v, head[u], w }; head[u] = tot++; } void dfs(int now, int fa) { depth[now] = depth[fa] + 1; father[now][0] = fa; for (int i = 1; i <= lg[depth[now]] + 1; ++i) father[now][i] = father[father[now][i - 1]][i - 1]; for (int i = head[now]; ~i; i = eg[i].next) if (eg[i].to != fa) { dis[eg[i].to] = dis[now] + eg[i].cost; dfs(eg[i].to, now); } } int lca(int u, int v) { if (depth[u] < depth[v]) swap(u, v); while (depth[u] != depth[v]) u = father[u][lg[depth[u] - depth[v]]]; if (u == v) return u; for (int i = lg[depth[u]]; i >= 0; --i) { if (father[u][i] != father[v][i]) { u = father[u][i]; v = father[v][i]; } } return father[u][0]; } int lca2(int u, int v,int len) { while (len){ int lsu=u; u = father[u][lg[len]]; len-=dis[lsu]-dis[u]; } return u; } int main() { lg[0] = -1; for (int i = 1; i < mac-5; ++i) lg[i] = lg[i >> 1] + 1, head[i] = -1; memset(father, 0, sizeof father); memset(depth, 0, sizeof depth); memset(dis, 0, sizeof dis); int k; scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= n - 1; ++i) { int u,v; scanf("%d%d", &u, &v); add(u,v,1);add(v,u,1); } dfs(1, 0); while (m--) { int s,t; scanf("%d%d", &s, &t); int id=lca(s,t); if (dis[s]-dis[id]+(dis[t]-dis[id])<=k) ans[t]++; else{ if (dis[s]-dis[id]==k) ans[id]++; else if (dis[s]-dis[id]>k){//在左链 id=lca2(s,id,k); ans[id]++; } else { id=lca2(t,id,(dis[t]-dis[id])-(k-(dis[s]-dis[id]))); //右链需要往上爬的长度为右链长-(k-左链的长度) ans[id]++; } } } for (int i=1; i<=n; i++) printf("%d%c",ans[i],i==n?' ':' '); return 0; }
神奇的中位数
题目大意:给你n个数,每给你一个数后让你求当前检查序列的中位数,假定初始的ans=0,每次给完一个数之后,你将该数异或上一次的答案放入检查序列,求给出每个数之后的当前检查序列的中位数
Sample Input
5 1 2 3 0 1
Sample Output
1 1 2 2 2
emmm,题目没看懂?没关系,我们写一份最暴力的代码来一发就知道是怎么回事了:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; int a[mac]; int main() { int n; scanf ("%d",&n); int cnt=0; int lastans=0; for (int i=1; i<=n; i++){ scanf("%d",&a[i]); a[i]^=lastans; sort(a+1,a+1+i); lastans=a[(i+1)/2]; printf("%d ",lastans); } return 0; }
。。。先来一波第二慢的二分做法,我们可以利用vector的insert函数加上二分查找来做。。。。只不过insert函数复杂度貌似挺高的,我试了一下,不行。。。不过比上面的做法过的测试点更多是肯定的
下面是T了的二分+vector做法
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; vector<int>g; int main() { int n; scanf ("%d",&n); int ans=0; g.push_back(-1); for (int i=1; i<=n; i++){ int x; scanf("%d",&x); int now=ans^x; //printf("now,ans=%d %d ",now,ans ); if (i==1) { printf("%d ",x); ans=x; g.push_back(x); continue; } int pos=lower_bound(g.begin(),g.end(),now)-g.begin(); /* if (pos<g.sise()-1) if (g[pos]>now) pos--;*/ g.insert(g.begin()+pos,now); //for (auto u:g) printf("%d ",u);printf(" "); ans=g[(i+1)/2]; printf("%d ",ans); } return 0; }
然后考虑到有序序列,我们可以想的multiset,这个玩意儿很nb,用multiset写一发,只T了四个点,而且超过的时间也不多好像。。。。
下面是T了的multiset做法
#include <bits/stdc++.h> using namespace std; multiset<int> st; multiset<int>::iterator it; void in(int &x) { int f=0; char ch=getchar(); while (ch>'9' || ch<'0') ch=getchar(); while (ch>='0' && ch<='9') f=(f<<3)+(f<<1)+ch-'0',ch=getchar(); x=f; } void out(int x) { if (x>=10) out(x/10); putchar(x%10+'0'); } int main() { int n; int x, lastans = 0; in(n); for (int i = 1; i <= n; ++i) { in(x); x = x ^ lastans; st.insert(x); if (i == 1) it = st.begin(); else { if (i & 1) { if (lastans <= x && it != st.end()) it++; } else { if (lastans > x && it != st.begin()) it--; } } lastans = *it; out(lastans); putchar(' '); } return 0; }
考虑到中位数算是序列第k大,那么我们可以试一下权值线段树,由于数值比较大,又是强制在线的,我们不能离散化,只能动态开点来一波,不过很遗憾的是,虽然跑得比multiset快一点,但还是T了四个点。。。大概在1100ms左右我的,只不过zhrt经过不断努力优化和利用测评姬的抖动终于挤进了1000ms,达到了980ms+。。。。
以下是T了的权值线段树动态开点做法(各位也可以优化一下A了)
#include <bits/stdc++.h> using namespace std; #define lson l,mid,ll[rt] #define rson mid+1,r,rr[rt] #define ls ll[rt] #define rs rr[rt] const int inf=2e9+10; const int mac=1e6+10; int ll[mac*32],rr[mac*32],sum[mac*32]; int sz=0,root=0; void in(int &x) { int f=0; char ch=getchar(); while (ch>'9' || ch<'0') ch=getchar(); while (ch>='0' && ch<='9') f=(f<<3)+(f<<1)+ch-'0',ch=getchar(); x=f; } void out(int x) { if (x>=10) out(x/10); putchar(x%10+'0'); } void update(int l,int r,int &rt,int pos) { if (!rt) rt=++sz; sum[rt]++; if (l==r) return; int mid=(1LL*l+r)>>1;//(特别注意,会爆int,蒟蒻在这里RE,MLE了十几发) if (mid>=pos) update(lson,pos); else update(rson,pos); } int query(int l,int r,int rt,int pos) { if (l==r) return l; int mid=(1LL*l+r)>>1; if (sum[ls]>=pos) return query(lson,pos); else return query(rson,pos-sum[ls]); } int main() { int n; int x, lastans = 0; in(n); for (int i = 1; i <= n; ++i) { in(x); x = x ^ lastans; if (i==1){ lastans=x; update(0,inf-5,root,x); } else { update(0,inf-5,root,x); int pos=(i+1)>>1; lastans=query(0,inf-5,root,pos); } out(lastans); putchar(' '); } return 0; }
接下来就是较为巧妙的优先队列做法(出题人解法)了,既然要找中位数我们只需要将序列均分为两部分存在优先队列q1,q2中,其中q1为小根堆队列(返回最大值),q2为大根堆队列(返回最小值)那么我们就可以巧妙得将序列分为$1-q1,q2-max$,那么中位数就要么是q1.top(),要么是q2.top()了。
接下来就是维护这两个优先队列了,也不是很难,每得到一个值之后我们先判断是否大于q1,如果是的话我们将其放入q2中,否则就放到q1中,接下来我们再检查q1和q2的长度,分为奇数偶数讨论,偶数就平分,那么答案就是q1.top(),奇数要么q1多一个数,要么q2多一个数,答案就是多的那个队列的top
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; int a[mac]; int main() { int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf("%d",&a[i]); priority_queue<int,vector<int>,greater<int> >q2;//返回最小值 priority_queue<int,vector<int>,less<int> >q1;//返回最大值 /*1-q1,q1-q2*/ int ans=a[1]; printf("%d ",a[1]); q1.push(a[1]); for (int i=2; i<=n; i++){ int now=a[i]^ans; if (now>q1.top()) q2.push(now); else q1.push(now); if (i&1){ if (q1.size()>q2.size()+1) { q2.push(q1.top()); q1.pop(); } else if (q2.size()>=q1.size()){ q1.push(q2.top()); q2.pop(); } ans=q1.top(); printf("%d ",q1.top()); } else{ if (q1.size()>q2.size()){ q2.push(q1.top()); q1.pop(); } else if (q2.size()>q1.size()){ q1.push(q2.top()); q2.pop(); } ans=q1.top(); printf("%d ",q1.top()); } } return 0; }