2019 Multi-University Training Contest 1
题号 | A | B | C | D | E | F | G | H | I | J | K | L | M |
状态 | . | Ø | . | Ο | Ο | . | . | . | Ø | . | . | . | . |
题意:给出n个数m个操作,操作1是往数组后面添加一个新元素x,操作0是在$[l,r]$区间内选取若干个元素,使得这些元素异或和最大,并且强制在线做法。、
$d[n][i]$表示第n位的第i个基是什么,$pos[n][i]$表示这个基的位置是什么,将数字插入线性基时,如果是之前没出现过的基,则直接记录位置,如果出现过,则从高位到低位,尽量用当前的基去替换之前的基,这样能使所有的基离r更近。
查询时,如果一个基的位置是大于等于l的,并且异或起来能使答案更大,则异或进答案。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1e6+10; int n,m; int pos[maxn][32],d[maxn][32]; void insert(int now,int val){ for(int i=0;i<=30;i++){ pos[now][i]=pos[now-1][i]; d[now][i]=d[now-1][i]; } int p=now; for(int i=30;i>=0;i--){ if(val&(1<<i)){ if(!d[now][i]){ d[now][i]=val; pos[now][i]=p; return; } if(pos[now][i]<p){ swap(p,pos[now][i]); swap(val,d[now][i]); } val^=d[now][i]; } } } int main(){ int T; cin>>T; while(T--){ clr(pos,0),clr(d,0); cin>>n>>m; for(int i=1;i<=n;i++){ int x; scanf("%d",&x); insert(i,x); } int lastans=0; while(m--){ int op,l,r,x; scanf("%d",&op); if(op==0){ scanf("%d%d",&l,&r); l=(l^lastans)%n+1; r=(r^lastans)%n+1; if(l>r){ swap(l,r); } lastans=0; for(int i=30;i>=0;i--){ if(pos[r][i]>=l&&(lastans^d[r][i])>lastans){ lastans^=d[r][i]; } } printf("%d\n",lastans); }else{ scanf("%d",&x); x=(x^lastans); n++; insert(n,x); } } } }
题意:给出n辆车距离终点的距离,车长,速度。当车撞上前面的车后,速度会和前面这辆车保持一致,求最后一辆车的车头经过终点所需要的时间。
思路:一个显然的结论,如果一辆车会撞上前面的车,那么最后经过终点的时间只和前面这辆车的速度有关,和本身的速度无关。所以我们用双指针,pos1代表当前车,pos2代表我要追赶的车,计算一下是否会在前面的车停下前碰到前面的车(我们假设最后一辆车的车头碰到终点时所有车辆瞬间停止),如果可以的话,那么pos1=pos2,pos2去找下一辆,如果不可以的话,pos2去找下一辆(当前的车不一定只会被前面的车影响)最终扫完找到的车就是最终状态的车速,简单计算可得答案。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100010; struct node{ double len,dis,v; }a[maxn]; int n; double sum[maxn]; bool check(int pos1,int pos2){ double t2=(a[pos2].dis+sum[pos2]-sum[1])/a[pos2].v; double t1=(a[pos1].dis+sum[pos1]-sum[1])/a[pos1].v; if(t1>t2)return false; return true; } int main(){ while(cin>>n){ n++; for(int i=1;i<=n;i++){ scanf("%lf",&a[i].len); sum[i]=sum[i-1]+a[i].len; } for(int i=1;i<=n;i++){ scanf("%lf",&a[i].dis); } for(int i=1;i<=n;i++){ scanf("%lf",&a[i].v); } int pos1=1,pos2=2,car=1; while(pos2<=n){ if(check(pos1,pos2)){ // printf("pos1:%d pos2:%d\n",pos1,pos2); car=pos2; pos1=pos2,pos2++; }else{ pos2++; } } // printf("car:%d\n",car); printf("%.10f\n",(a[car].dis+sum[car]-sum[1])/a[car].v); } }
1005 Path
题意:给出有向图,求阻塞若干条边,使得最短路变长或者阻塞,代价为阻塞的边长和,要求代价最小。
思路:先跑出所有的最短路,把属于最短路的边重新建图,原问题等价于求最小割。
这个想法很简单,但一万个点我不敢写,赛后想一下,感觉最短路的边可能很多,但一定不会是复杂度(即不会有很多拐弯的环),这样找网络流的增广路径的次数不会太多,所以不会超时(强行解释)
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=10010; ll inf=0x3f3f3f3f3f3f3f3f; struct node { int u; ll w; friend bool operator<(const node &a,const node &b) { return a.w>b.w; } }; priority_queue<node >q; ll dis[2][maxn]; int n,m,u,v,tot[2],head[2][maxn]; ll w; struct edge { int to,Next; ll w; } a[2][maxn<<1]; struct ed{ int u,v; ll w; }in[maxn]; void init() { tot[0]=tot[1]=0; for(int i=1; i<=n; i++) { dis[0][i]=dis[1][i]=inf; head[0][i]=head[1][i]=-1; } } void addv(int u,int v,ll w,int typ) { a[typ][++tot[typ]].to=v; a[typ][tot[typ]].w=w; a[typ][tot[typ]].Next=head[typ][u]; head[typ][u]=tot[typ]; } void dij(int s,int t,int typ){ dis[typ][s]=0; priority_queue<node >q; q.push({s,0}); while(!q.empty()){ node st=q.top(); q.pop(); int u=st.u; for(int i=head[typ][u];i!=-1;i=a[typ][i].Next){ int v=a[typ][i].to; if(dis[typ][v]>dis[typ][u]+a[typ][i].w){ dis[typ][v]=dis[typ][u]+a[typ][i].w; q.push({v,dis[typ][v]}); } } } } struct Edge { int to; ll flow; int nxt; Edge() {} Edge(int to, int nxt, ll flow):to(to),nxt(nxt), flow(flow) {} } edge[maxn << 2]; int hd[maxn], dep[maxn]; int S, T; int N, tol; void Init(int n) { N = n; for (int i = 0; i <=N; ++i) hd[i] = -1; tol = 0; } void add(int u, int v, ll w, ll rw = 0) { edge[tol] = Edge(v, hd[u], w); hd[u] = tol++; edge[tol] = Edge(u, hd[v], rw); hd[v] = tol++; } bool BFS() { for (int i = 0; i <= N; ++i) dep[i] = -1; queue<int>q; q.push(S); dep[S] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = hd[u]; ~i; i = edge[i].nxt) { if (edge[i].flow && dep[edge[i].to] == -1) { dep[edge[i].to] = dep[u] + 1; q.push(edge[i].to); } } } return dep[T] < 0 ? 0 : 1; } ll DFS(int u, ll f) { if (u == T || f == 0) return f; ll w, used = 0; for (int i = hd[u]; ~i; i = edge[i].nxt) { if (edge[i].flow && dep[edge[i].to] == dep[u] + 1) { w = DFS(edge[i].to, min(f - used, edge[i].flow)); edge[i].flow -= w; edge[i ^ 1].flow += w; used += w; if (used == f) return f; } } if (!used) dep[u] = -1; return used; } ll Dicnic() { ll ans = 0; while (BFS()) { ans += DFS(S, inf); } return ans; } int main(){ int TT; cin>>TT; while(TT--){ cin>>n>>m; init(); for(int i=1;i<=m;i++){ scanf("%d%d%lld",&in[i].u,&in[i].v,&in[i].w); addv(in[i].u,in[i].v,in[i].w,0); addv(in[i].v,in[i].u,in[i].w,1); } dij(1,n,0); dij(n,1,1); tot[0]=0; for(int i=1; i<=n; i++) { head[0][i]=-1; } S=1,T=n; Init(n); for(int i=1;i<=m;i++){ int u=in[i].u,v=in[i].v; ll w=in[i].w; if(dis[0][u]+w+dis[1][v]==dis[0][n]){ add(u,v,w); } } printf("%lld\n",Dicnic()); // printf("debug\n"); } }
1009 String
题意:给出一个字符串,要求从中取出长度为k的子序列,使得子序列中abc等26个字符的个数在$(li,ri)$之间,并且要求字典序最小。
思路:
官方题解:一位位地构造答案字符串,每次贪心地加能加入的最小的字符 (判断能否加入只要判断加入之后原字符串剩下的后缀中的每种字符的数目能否足够满足条件)。
其实就是一次构造答案字符串的1-k位,每次都从a考虑,假设使用了a之后,判断之后26个字母能否都满足左区间,再判断当前已经使用的再加上还需要使用的是否大于右区间。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; typedef pair<ll,ll> pll; const int maxn=100010; char s[maxn],ans[maxn]; int n,k; vector<int >ve[26]; int head[26]; int l[26],r[26],cnt[maxn][26],siz[26],used[26]; void init(){ for(int i=0;i<26;i++){ ve[i].clear(); cnt[n][i]=0; head[i]=0; siz[i]=0; used[i]=0; } } int main(){ while(scanf("%s%d",s,&k)!=EOF){ for(int i=0;i<26;i++){ scanf("%d%d",&l[i],&r[i]); } n=strlen(s); init(); for(int i=0;i<n;i++){ ve[s[i]-'a'].push_back(i); siz[s[i]-'a']++; } for(int i=n-1;i>=0;i--){ for(int j=0;j<26;j++){ cnt[i][j]=cnt[i+1][j]+(s[i]-'a'==j); } } int isok=0,last=-1; for(int i=0;i<k;i++){ for(int j=0;j<26;j++){ if(used[j]==r[j])continue; while(ve[j][head[j]]<=last&&head[j]<siz[j])head[j]++; if(head[j]==siz[j])continue; bool flag=1; used[j]++; int pos=ve[j][head[j]]; int sum1=0,sum2=0; for(int x=0;x<26;x++){ sum1+=max(l[x]-used[x],0); sum2+=min(cnt[pos+1][x],r[x]-used[x]); if(l[x]-used[x]>cnt[pos+1][x])flag=0; } if(sum1>k-i-1)flag=0; if(sum2<k-i-1)flag=0; if(!flag)used[j]--; else{ isok=1; last=pos; ans[i]='a'+j; break; } } if(!isok)break; // printf("i:%d\n",i); } if(isok){ for(int i=0;i<k;i++){ printf("%c",ans[i]); } puts(""); }else{ puts("-1"); } } }