水题略过
#include<cstdio> #include<cstdlib> #include<cmath> using namespace std; typedef long long int LL; int main() { LL a,b; scanf("%I64d%I64d",&a,&b); LL num=1; while(true) { if(a<num){printf("Vladik ");return 0;} a-=num; //b+=num; num++; if(b<num){printf("Valera ");return 0;} b-=num; //a+=num; num++; } return 0; }
B. Vladik and Complicated Book
给定一个序列, 询问子区间【l,r】的第k小数是不是原位置的数。
显然可以用主席树维护,不过这道题数据放水了,用暴力一点的方法应该也能过。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100000 + 5; int a[N], b[N], rt[N * 20], ls[N * 20], rs[N * 20], sum[N * 20],numm[N]; int n, k, tot, sz, ql, qr, x, q, T; void Build(int& o, int l, int r){ if(l>r)return ; o = ++ tot; sum[o] = 0; if(l == r) return; int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); } void update(int& o, int l, int r, int last, int p){ o = ++ tot; ls[o] = ls[last]; rs[o] = rs[last]; sum[o] = sum[last] + 1; if(l == r) return; int m = (l + r) >> 1; if(p <= b[m]) update(ls[o], l, m, ls[last], p); else update(rs[o], m + 1, r, rs[last], p); } int query(int ss, int tt, int l, int r, int k){ if(l == r) return l; int m = (l + r) >> 1; int cnt = sum[ls[tt]] - sum[ls[ss]]; if(k <= cnt) return query(ls[ss], ls[tt], l, m, k); else return query(rs[ss], rs[tt], m + 1, r, k - cnt); } void work(){ scanf("%d%d%d", &ql, &qr, &x); int xx=x; x=(xx-ql+1); int ans = query(rt[ql - 1], rt[qr], 1, sz, x); if(b[ans]==numm[xx])printf("Yes "); else printf("No "); } int main(){//freopen("t.txt","r",stdin); T=1; while(T--){ scanf("%d%d", &n, &q); for(int i = 1; i <= n; i ++) scanf("%d", a + i), numm[i]=b[i] = a[i]; sort(b + 1, b + n + 1); sz = unique(b + 1, b + n + 1) - (b + 1); tot = 0; Build(rt[0],1, sz); //for(int i = 0; i <= 4 * n; i ++)printf("%d,rt = %d,ls = %d, rs = %d, sum = %d ", i, rt[i], ls[i], rs[i], sum[i]); //for(int i = 1; i <= n; i ++)a[i] = lower_bound(b + 1, b + sz + 1, a[i]) - b; for(int i = 1; i <= n; i ++)update(rt[i], 1, sz, rt[i - 1], a[i]); // for(int i = 0; i <= 5 * n; i ++)printf("%d,rt = %d,ls = %d, rs = %d, sum = %d ", i, rt[i], ls[i], rs[i], sum[i]); while(q --)work(); } return 0; }
O(n^2)的线性dp 不要想太多。。(我都想到DAG去了。。。)
代码比较简单,可以直接看懂。
#include <bits/stdc++.h> using namespace std; vector <int> ps [5010], cs[5010]; int s[5010] , e[5010]; bool vis[5010]; long long dp [5010]; int main() { int n; scanf("%d",&n); vector <int> a (n); for (int i = 0; i < n; i++) { scanf("%d",&a[i]); if (!vis[a[i]]) s[a[i]] = i; vis[a[i]] = 1; e[a[i]] = i; } for (int i = 0; i < n; i++) vis[a[i]] = 0; for (int i = 0; i < n; i++) { int mx = i, rx = 0, lj = i; for (int j = i; j < n; j++) { if (s[a[j]] < i) break; lj = j; mx = max(mx,e[a[j]]); if (!vis[a[j]]) rx ^= a[j]; vis[a[j]] = 1; if (mx <= j) { ps[i].push_back(j); cs[i].push_back(rx); } } for (int k = lj; k >= i; k--) vis[a[k]] = 0; } for (int i = n-1; i >= 0; i--) { dp[i] = dp[i+1]; for (int j = 0; j < ps[i].size(); j++) { dp[i] = max(dp[i],dp[ps[i][j]+1]+cs[i][j]); } } printf("%I64d ",dp[0]); }
第一步判断一下按钮是否反转即可。剩下的直接暴力求路径,水题。
#include<bits/stdc++.h> using namespace std; #define mp make_pair #define ff first #define ss second #define pb push_back const int N = 100 + 11; int use[N][N]; char a[N][N],b[5]; vector<pair<int,int> > v,w; vector<int> vv; void dfs(int l, int r) { use[l][r]=1; v.pb(mp(l,r)); if (a[l][r]=='F') {w=v; return;} if (use[l-1][r]==0&&(a[l-1][r]=='.'||a[l-1][r]=='F')) dfs(l-1,r); if (use[l+1][r]==0&&(a[l+1][r]=='.'||a[l+1][r]=='F')) dfs(l+1,r); if (use[l][r-1]==0&&(a[l][r-1]=='.'||a[l][r-1]=='F')) dfs(l,r-1); if (use[l][r+1]==0&&(a[l][r+1]=='.'||a[l][r+1]=='F')) dfs(l,r+1); v.pop_back(); } int main() { int n,m; cin>>n>>m; int l,r; for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) { cin>>a[i][j]; if (a[i][j]=='F') {l=i; r=j;} } b[1]='L'; b[2]='R'; b[3]='U'; b[4]='D'; dfs(1,1); for (int i=1; i<w.size(); i++) { if (w[i].ff==w[i-1].ff) { if (w[i].ss==w[i-1].ss-1) vv.pb(1); else vv.pb(2); } else if (w[i].ff==w[i-1].ff-1) vv.pb(3); else vv.pb(4); } int u1=0,u2=0; int x=1,y=1; for (int i=0; i<vv.size(); i++) { if (vv[i]==2&&u1==0) { cout<<"R"<<endl; int xx,yy; cin>>xx>>yy; u1=1; if (x==xx&&y==yy) {swap(b[1],b[2]); cout<<"L"<<endl; cin>>x>>y;} } else if (vv[i]==4&&u2==0) { cout<<"D"<<endl; int xx,yy; cin>>xx>>yy; u2=1; if (x==xx&&y==yy) {swap(b[3],b[4]); cout<<"U"<<endl; cin>>x>>y;} } else { cout<<b[vv[i]]<<endl; cin>>x>>y; } } }
E. Vladik and Entertaining Flags
线段树+并查集
给定一个行数为10 列数10w的矩阵,每个方块是一个整数, 给定l和r 求范围内的联通块数量 所谓联通块即数字相等。
考虑用二分的方法合并两个联通块,在合并的时候只需要标记边界的元素属于哪个块即可,不用考虑块内的元素,因为它们不会在将来的合并中使用到。
这样我们就可以做到在常数时间内合并两个块了,这样用线段树维护就没有问题了。
其实想了一下,用倍增的方法也是可以的,dp[i][k]表示从第i列开始 连续2^k列的联通块,复杂度也是O(nlogn)
所以用一个线段树维护所有的[l,r]子块,每次查询线段树即可,合并的时候要重新标号。
重点:对于每个子块,我们只需要知道边界上的元素属于这个子块的哪个联通分量即可(使用并查集维护)
比赛的时候我也是老想块中的元素怎么维护,就觉得二分难搞了,其实完全不需要考虑。。
#include <bits/stdc++.h> using namespace std; const int N = 10; const int M = 100010; int n, m, q; int a[N][M]; int state[50], mark[50]; struct Info{ int cnt; int left[N]; int right[N]; Info() { cnt = 0; memset(left, 0, sizeof left); memset(right, 0, sizeof right); } } tr[M << 2]; Info join(Info &u, Info &v, int p) { Info f; f.cnt = u.cnt + v.cnt; memset(state, 0, sizeof state); for (int k = 0; k < n; k++) { if (a[k][p] == a[k][p + 1]) { int x = u.right[k]; int y = v.left[k] + 21; while (state[x]) { x = state[x]; }//路径压缩 while (state[y]) { y = state[y]; } if (x != y) { f.cnt--; state[y] = x; } } } int g = 1; memset(mark, 0, sizeof mark); for (int k = 0; k < n; k++) { int x = u.left[k]; while (state[x]) { x = state[x]; } if (mark[x] == 0) { mark[x] = g++; }//从头标号 只需记录左边界和右边界 f.left[k] = mark[x];//重新标记边界 int y = v.right[k] + 21; while (state[y]) { y = state[y]; } if (mark[y] == 0) { mark[y] = g++; } f.right[k] = mark[y];//重新标记边界 } return f; } void build(int l, int r, int i) { if (l == r) { for (int k = 0; k < n; k++) { if (k == 0 || a[k][l] != a[k - 1][l]) { tr[i].left[k] = tr[i].right[k] = ++tr[i].cnt; } else { tr[i].left[k] = tr[i].right[k] = tr[i].left[k - 1]; } } } else { int mid = (l + r) >> 1; build(l, mid, i + i); build(mid + 1, r, i + i + 1); tr[i] = join(tr[i + i], tr[i + i + 1], mid); } /* cout << " + " << l << " " << r << " "; for (int k = 0; k < n; k++) { cout << tr[i].left[k] << " "; } cout << " "; for (int k = 0; k < n; k++) { cout << tr[i].right[k] << " "; } cout << " "; */ } void get(int l, int r, int x, int y, int i, Info &res) { if (x <= l && r <= y) { if (l == x) { res = tr[i]; } else { res = join(res, tr[i], l - 1); } } else { int mid = (l + r) >> 1; if (mid >= x) { get(l, mid, x, y, i + i, res); } if (mid < y) { get(mid + 1, r, x, y, i + i + 1, res); } } } int main() { scanf("%d %d %d", &n, &m, &q); for (int i = 0; i < n; i++) { for (int j = 1; j <= m; j++) { scanf("%d", &a[i][j]); } } build(1, m, 1); Info res; while (q--) { int l, r; scanf("%d %d", &l, &r); get(1, m, l, r, 1, res); printf("%d ", res.cnt); } }