开坑难填之A层邀请赛1
作者:互联网
A. Race
据说很容易想到Trie树?但我当时只想到了暴力……(原因是Trie树还不会qwq)
//我相信我没分~ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 3; const ll mod = 998244353; int m, n, a[maxn]; ll r[maxn], ans; struct node { int id; ll val; bool operator < (const node &T) const { return T.val < val; } }c[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { n = read(); m = read(); for(int i=1; i<=n; i++) a[i] = read(); int Day = (1<<m); for(int j=0; j<Day; j++) { //printf("j = %d\n", j); for(int i=1; i<=n; i++) { c[i].val = (a[i]^j); c[i].id = i; //printf("c[%d].val = %lld\n", i, c[i].val); } sort(c+1, c+1+n); /*for(int i=1; i<=n; i++) { printf("%lld\n", c[i].val); }*/ for(int i=1; i<=n; i++) { int id = c[i].id; r[id] = (r[id]+(ll)(i-1)*(i-1))%mod; } } for(int i=1; i<=n; i++) { ans ^= r[i]; //printf("r[%d] = %lld\n", i, r[i]); //printf("ans = %lld\n", ans); } printf("%lld", ans); return 0; }TLE 10
正解:先把x^2拆开变成(x1+x2+x3+...)*(x1+x2+x3+...)其中每一个xi都为1,代表当天更大的数,再继续展开就变成了(x1^2+x2^2+...+2*x1*x2+2*x1*x3+...),所以答案就和在它之前的点对的个数有了关系。
对每一个a[i],枚举在a[i]之前的点对,其中一个是从M-1到j-1异或值和a[i]相同,另一个是从M-1到k-1和a[i]相同,它们分别在第j位和第k位比a[i]的对应位大,find是找到前面的对应位相同,ch^1是当前位和a[i]相反,而单独找到相反还不够可能异或更小了,这就给天数设置了限制条件,一定可以找到满足条件的日子因为它所有位都是满的。j==k的情况是1<<(M-1)因为对M的限制只有一位。
这个应该不算是按位计算贡献吧,只是把位拆开来枚举点对而已……不过这么说好像也可以?
找点对的时候避免重复把k循环到j-1而不是把k==j continue掉。点对当然可以合并(在计算贡献时用树上的size),因为点对本来就是拆开的,拆成几个都一样。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e5 + 5; const int N = 1e7 + 2; const int mod = 1e9 + 7; const int INF = 1061109567; int n, M, tot=1, fa[maxn][31]; ll ans[maxn], sum, a[maxn]; struct node { ll ch[2], size; }t[maxn*20]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void insert(int x) { int p = 1; t[p].size++; fa[x][M] = 1; for(int i=M-1; i>=0; i--) { int me = (a[x]>>i)&1; if(!t[p].ch[me]) t[p].ch[me] = ++tot; p = t[p].ch[me]; t[p].size++; fa[x][i] = p; } } void work(int x) { for(int i=0; i<=M-1; i++) { int me1 = 0, find1 = 0, me2 = 0, find2 = 0; for(int j=0; j<i; j++) { me1 = (a[x]>>i)&1; me2 = (a[x]>>j)&1; me1^=1; me2^=1; find1 = fa[x][i+1]; find2 = fa[x][j+1]; ans[x] = (ans[x]+2*t[t[find1].ch[me1]].size*t[t[find2].ch[me2]].size%mod*(1ll<<(M-2ll))%mod)%mod; } me1 = (a[x]>>i)&1; me1^=1; find1 = fa[x][i+1]; ans[x] = (ans[x]+t[t[find1].ch[me1]].size*t[t[find1].ch[me1]].size%mod*(1ll<<(M-1ll))%mod)%mod; } } int main() { n = read(); M = read(); for(int i=1; i<=n; i++) { a[i] = read(); } for(int i=1; i<=n; i++) { insert(i); } for(int i=1; i<=n; i++) { work(i); } for(int i=1; i<=n; i++) { sum ^= ans[i]; } printf("%lld\n", sum); return 0; }View Code
B. 青蛙
赛时正解好像我也想到了,不会数cnt+1……WA 0……
如果这些石头连一只青蛙不付代价地跳过去都不够,那就让代价最小的青蛙把所有石头跳一遍,其他青蛙直接从头到尾;如果这些石头可以让至少一只青蛙不付代价地跳过去,那就尽可能让代价大的去跳石头,每次卡着上限去跳给其他青蛙也留足机会,不用考虑石头有剩余,直接分配给能跳过去的某只青蛙就好了,距离只会减得更小,一次跳不过去的青蛙直接从头到尾。
那种卡上限跳的思路可以用set实现,虽然有log不过据说可以过,但当时这么想纯属瞎蒙,并不会证明,于是我去借鉴了双指针的方法——%%%Chen_jr%%%妙不可言
讲题那天好像就是这么讲的,蒟蒻直到看见代码才知道说的是什么意思……
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e5 + 5; const int N = 1e7 + 2; const int mod = 1e9 + 7; const int INF = 1061109567; int T, n, m, k, d, c[maxn], a[maxn]; bool f[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { T = read(); while(T--) { n = read(); m = read(); k = read(); d = read(); for(int i=1; i<=m; i++) { c[i] = read(); } for(int i=1; i<=k; i++) { a[i] = read(); } sort(a+1, a+k+1); sort(c+1, c+m+1); int p = 0, cnt = 0; for(int i=0; i<=k; i++) f[i] = 0;//memset for(; p<=k; p++) { if(a[p]-1 <= d) f[p] = 1;//表示有青蛙跳到了第p个石头上 else break; } for(int i=1; i<=k; i++) { //为什么不判断f[p]不能是1,因为p跳完就往后增加,当然不可能是1 if(f[i] && a[p]-a[i]<=d && p <= k)//从有青蛙的石头开始向后转移,最靠左的青蛙尝试最近的空石头 { f[p] = 1; f[i] = 0; p++;//青蛙跳走了记得清空 } } for(int i=k; i>=1; i--)//然后还要能到终点才行 { if(n-a[i] <= d && f[i]) cnt++; else break; } ll ans = 0; if(cnt == 0) { a[0] = 1; a[k+1] = n; for(int i=1; i<=k+1; i++) { if(a[i]-a[i-1] > d) ans += c[1]; } ans -= c[1];//因为后面没有else,c[1]又加了一遍 } for(int i=1; i<=m-cnt; i++) { ans += c[i]; } printf("%lld\n", ans); } return 0; }View Code
C. 序列
首先,暴力可以水个25分。然后,吉斯机线段树是什么鬼啊,容我先去学习一下……
标签:ch,const,int,ll,开坑,maxn,ans,邀请赛 来源: https://www.cnblogs.com/Catherine2006/p/16584801.html