比赛经历:摸鱼划水了一个多小时又是只会签到,看来还得提升自己的解题能力写了六题
补题:E线段树维和区间平方和,比较经典好久没写过线段树了傻了,注意维护lazy
J计算几何,看来得提上日程了,用叉积判断位置
k题意看起来难爆了,没想到竟然做法如此优秀,所以有些题还是得多尝试
目录
A.组队分配
B.两点距离
C.轮到谁了?
E.线段树
F.最长回文串
G.管管的幸运数字
I.鸽子的整数运算
J.鸽者文明的三体问题
K.xor
A.组队分配
文字游戏
注意有些简单题,题目说法就是暗藏玄机,比如本题就是如此,注意按照排名升序,同时注意到内部排序是用降序
int t,n,m;
struct code{string s;int x;bool operator<(const code&t)const{return x<t.x;}
}e[N];void solve(){cin>>n;for(int i=1;i<=3*n;i++){string s; cin>>s;int x; cin>>x;e[i]={s,x};}sort(e+1,e+1+3*n);int cnt = 0;for(int i=1;i<=3*n;i+=3,cnt++){auto [s1,x1]=e[i];auto [s2,x2]=e[i+1];auto [s3,x3]=e[i+2];cout << "ACM-" << cnt << ' ' << s3 << ' ' << s2 << ' ' << s1 << endl;}return ;
}
B.两点距离
小小思考一手
注意题目定义我们可以发现两个点之间最大的距离就是2(分别到1),最小的距离就是__gcd,同时注意在一起即可,这种题要留个心眼想全再提交
void solve(){cin>>n>>m;while(m--){int x,y; cin>>x>>y;if(x>y) swap(x,y);if(x==y){cout << 0 << endl;continue;}cout << min(2,__gcd(x,y)) << endl;}return ;
}
C.轮到谁了?
斐波那契数列
通过兔子问题以及简单分析可以得知是斐波那契问题,但是即使没得到这个结论我们可以按照要求模拟,定义a,b,c为成年的,一个月,两个月的兔子来模拟即可
void solve(){cin>>n>>m;int a = 1 ,b = 0 , c = 0;// 表示成年有多少 一个月的有多少 两个月的有多少for(int i=3;i<=n;i++){a = a + c; // 表示上次两个月到现在已经成年了 c = b; // 一个月变两个月b = a; //a %= m;b %= m;c %= m;}int ans = ((a+b+c-1)%m+m)%m; // 转化为编号cout << ans << endl;return ;
}
E.线段树
区间平方和
注意只要是区间问题中出现一个式子,我们都需要对式子进行推演变形,变成有利于我们做计算的,可以按照经验,一般可以为前缀和做差,预处理,莫队,线段树维护等,对于本题进行推理之和可以得到
j接着我们就是维护区间和 和区间平方和,同时题目要求的区间修改乘法和加法,我们可以依靠先对乘法处理,后面的加法*mul,同时维护,对于平方和对式子变化得到
sum1 表示区间和,sum2表示区间平方和,注意到乘一个数之后再做加法,所以先乘之后sum1会变化,所以sum1需要乘mul,sum1的变化同上
LL w[N];
struct code{int l,r;LL sum1,sum2; // 表示 总和LL add,mul; // 表示平方和
}tr[4*N];void eval(code&u,LL add,LL mul){u.sum2=(u.sum2*mul%p*mul%p+2*u.sum1*mul%p*add%p+(LL)(u.r-u.l+1)*add%p*add%p)%p;u.sum1=((LL)u.sum1*mul%p+(LL)(u.r-u.l+1)*add%p)%p;u.mul=(LL)(u.mul)%p*mul%p;u.add=((LL)u.add%p*mul%p+add)%p;
}void pushup(code&u,code&l,code&r){u.sum1 = (l.sum1 + r.sum1)%p;u.sum2 = (l.sum2 + r.sum2)%p;
}void pushdown(code&u,code&l,code&r){eval(l,u.add,u.mul);eval(r,u.add,u.mul);u.add=0,u.mul=1;
}void pushup(int u){pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}void pushdown(int u){pushdown(tr[u],tr[u<<1],tr[u<<1|1]);
}void build(int u,int l,int r){if(l==r){tr[u]={l,r,w[l],w[l]*w[l]%p};return ;}tr[u]={l,r,0,0,0,1};int mid=l+r>>1;build(u<<1,l,mid),build(u<<1|1,mid+1,r);pushup(u);
}void modify(int u,int l,int r,int mul,int add){if(tr[u].l>= l && tr[u].r<=r){eval(tr[u],add,mul);return ;}pushdown(u);int mid=tr[u].l+tr[u].r>>1;if(l<=mid) modify(u<<1,l,r,mul,add);if(r>mid) modify(u<<1|1,l,r,mul,add);pushup(u);
}code query(int u,int l,int r){if(tr[u].l>=l && tr[u].r<=r){return tr[u];}pushdown(u);int mid=tr[u].l+tr[u].r>>1;if(r<=mid) return query(u<<1,l,r);else if(l>mid) return query(u<<1|1,l,r);else{code res;code ll=query(u<<1,l,r);code rr=query(u<<1|1,l,r);pushup(res,ll,rr);return res;}
}
void solve(){auto qmi = [&](LL a,LL b,LL p){LL res = 1;while(b){if(b&1) res=res*a%p;b>>=1;a=a*a%p;}return res;};cin>>n>>m>>p;LL inv2 = qmi(2,p-2,p);for(int i=1;i<=n;i++) cin>>w[i],w[i]%=p;build(1,1,n);while(m--){int op,l,r,v; cin>>op>>l>>r;if(op==1){cin>>v; v%=p;modify(1,l,r,1,v);}else if(op==2){cin>>v; v%=p;modify(1,l,r,v,0);}else{code t = query(1,l,r);LL ans1 = t.sum1*t.sum1%p;LL ans2 = t.sum2;LL ans = ans1 - ans2;ans *= inv2;ans = (ans%p+p)%p;cout << ans << endl;}}return ;
}
F.最长回文串
简单贪心
题目咋一看很难,但是最忌讳就是咋一看可能就看错很多信息,对于有些字符串可以丢弃,对于每个字符串字符可以重新排列,最后字符串可以重新排列最后构成的最长的回文串
分析得出 一个串留下来的条件
1.有一个和他一模一样的放两边构成回文
2.当前这个可以作为回文串中间位置
我们直接排序判断是不是一样即可,对于中间的判断其奇数个数是不是小于等于1即可
void solve(){unordered_map<string,int> mp;cin>>n>>m;for(int i=1;i<=n;i++){string s; cin>>s;sort(s.begin(),s.end());mp[s]++;}int ans = 0;vector<string> a;for(auto&[v,w]:mp){ans += (int)v.size()*(w-(w&1));if(w&1) a.push_back(v);}int add = 0;for(auto&v:a){vector<int> cnt(26);for(auto&c:v) cnt[c-'a']++;int odd = 0;for(int i=0;i<26;i++) if(cnt[i]&1) odd++;if(odd<=1){add = m; break;}}ans += add;cout << ans << endl;return ;
}
G.管管的幸运数字
暴力
有些时候可能有好的做法,但是如果你发现你是用暴力的做法时间复杂度是可以轻松过去的时候也可以考虑暴力,比如本题我们可以得出结论每一个数的左右移动次数不会超过10000,轻松可以跑过直接暴力即可,首先用线性筛,然后直接左右遍历
bool st[N];
int p[N],cnt;void get(){for(int i=2;i<M;i++){if(!st[i]) p[cnt++]=i;for(int j=0;p[j]<M/i;j++){st[i*p[j]]=true;if(i%p[j]==0) break;}}
}
void solve(){cin>>n;if(!st[n]){cout << "YES" << endl;return ;}int ans = N;for(int i=n;i>=1;i--){if(!st[i]){ans = min(ans,n-i);break;}}for(int i=n+1;i;i++){if(!st[i]){ans = min(ans,i-n);break;}}cout << ans << endl;return ;
}
I.鸽子的整数运算
纯签到
没什么好说的直接模拟
void solve(){int op,a,b; cin>>op>>a>>b;int ans = 0;if(op==1) ans = a+b;if(op==2) ans = a-b;if(op==3) ans = a*b;if(op==4) ans =a/b;cout << ans << endl;return ;
}
J.鸽者文明的三体问题
叉积处理内外
我们可以考虑直接使用叉积来判断是不是在三角形的同一侧即可
struct code{int x[3],y[3];
}e[N];void solve(){cin>>n>>m;for(int i=1;i<=n;i++){for(int j=0;j<3;j++){cin>>e[i].x[j]>>e[i].y[j];}}auto check = [&](int x,int y,code t){auto get = [&](int x1,int y1,int x2,int y2){LL ans = (LL)x1*y2 - (LL)x2*y1;return ans;};int t1 = get(x-t.x[0],y-t.y[0],t.x[1]-t.x[0],t.y[1]-t.y[0]);int t2 = get(x-t.x[1],y-t.y[1],t.x[2]-t.x[1],t.y[2]-t.y[1]);int t3 = get(x-t.x[2],y-t.y[2],t.x[0]-t.x[2],t.y[0]-t.y[2]);return (t1>0) == (t2>0) and (t2>0)==(t3>0); };while(m--){int x,y; cin>>x>>y;int ans = 0;for(int i=1;i<=n;i++) if(check(x,y,e[i])) ans++;cout << (ans&1 ? "Yes" : "No") << endl;}return ;
}
K.xor
妙妙dp
对于本题题目看起来十分复杂,我们看看能不能简单处理,明显的是一个dp同时时间复杂度只能在nlogn级别,我们直接来定义一手dp[i]表示前i个构成符合题意的方案数,当前异或为a[i]我们来看当前这个数可以从什么状态转移过来,前缀中符合要求的能dp[j],异或为x^a[i]的,注意到我们无法从某一个位置转移过来,但是我们要的是异或为x^a[i]的位置,我们可以再定义一个dp,注意到数据范围可以开map来处理,定义为当前数为x的符合题目要求的方案数即可,map[x] 从当前数是x的位置转移过来不重不漏
int a[N],dp[N];void solve(){cin>>n>>x;map<int,int> mp;mp[0]=1;for(int i=1;i<=n;i++){cin>>a[i];a[i] ^= a[i-1];dp[i] = mp[x^a[i]];mp[a[i]] += dp[i];dp[i] %= mod;mp[a[i]] %= mod;}cout << dp[n] << endl;return ;
}