星期一:
补24牛客多校 三 J 倍增 牛客传送门
思路:因为是一个循环的字符串,想完全摊开长度可以达到4e10,无法用线段树处理,遂倍增
f【i】【j】表示从 i开始,进行 2^j局中赢了多少局,g【i】【j】表示从 i开始,2^j局后下局从哪开
先处理出 f【i】【0】和 g【i】【0】,可以双指针或二分,得到后即可倍增处理
然后对每个位置寻找答案,j从大到小搜,在总局数不超过 2*b的情况下,判断出谁先赢 b局
这里将 t扩展为>=4*a的长度,不知为何错了 ps:其实错的挺明显,长度不够
代码如下:
const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int sum[N];
int f[N][20],g[N][20];
int p2[20];
void solve(){
p2[0]=1;
for(int i=1;i<20;i++) p2[i]=p2[i-1]*2;
int a,b; cin >> n >> a >> b;
string s,t; cin >> s;
while((int)t.size()<4e5) t.append(s);
sum[0]=t[0]-'0';
int tsz=t.size();
for(int i=1;i<tsz;i++){
sum[i]=sum[i-1];
if(t[i]=='1') sum[i]++;
}
for(int i=0;i<n;i++){
int l=i,r=tsz-1,res=0;
int su=0;
while(l<=r){ //二分找第一局结束的地方
int mid=l+r>>1; su=0;
if(i) su=sum[i-1];
int cnt1=sum[mid]-su;
if(cnt1>=a || mid-i+1-cnt1>=a) res=mid,r=mid-1;
else l=mid+1;
}
if(!i) su=0; else su=sum[i-1];
if(sum[res]-su>=a) f[i][0]=1;
else f[i][0]=0;
g[i][0]=(res+1)%n; //res记得加一
}
for(int j=1;j<20;j++){
for(int i=0;i<n;i++){
f[i][j]=f[i][j-1]+f[g[i][j-1]][j-1];
g[i][j]=g[g[i][j-1]][j-1]; //倍增处理,划重点
}
}
for(int i=0;i<n;i++){
int p=i,cnt=0,win=0; //总局数和赢的局数
for(int j=19;j>=0;j--) if(cnt+p2[j]<2*b){
win+=f[p][j];
cnt+=p2[j];
p=g[p][j];
if(win>=b || cnt-win>=b) break;
}
if(win>=b) cout << 1;
else cout << 0;
}
}
补24牛客多校 三 D 构造 牛客传送门
题意:给 n个pair值,要求顺序排列使得 a[ i ].second != a[ i+1 ].first对于 i<n成立
思路:若存在数字出现 >n+1次必定无解,否则有解
若无pair值 x==y,则随便放都能满足条件,所以先考虑 x==y的pair怎么放
将x==y的pair放入堆中,每次放置优先考虑堆中最多的pair值
代码如下:
const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
PII a[N];int idx;
void solve(){
cin >> n;
map<int,int>mp,sa; int ma=0;
priority_queue<PII>pq;
for(int i=1;i<=n;i++){
int x,y; cin >> x >> y;
if(x>y) swap(x,y);
ma=max(++mp[x],ma);
ma=max(++mp[y],ma);
if(x==y) sa[x]++;
else a[++idx].first=x,a[idx].second=y;
}
sort(a+1,a+idx+1);
if(ma>n+1){cout << "NO\n"; return ;}
else cout << "YES\n";
for(auto [x,y]:sa) pq.push({y,x});
int lst=0;
while(!pq.empty()){
vector<PII>ve;
while(!pq.empty() && pq.top().second==lst){
ve.push_back(pq.top());
pq.pop();
}
if(!pq.empty()){
auto [x,y]=pq.top(); pq.pop();
cout << y << " " << y << "\n";
lst=y;
if(x>1) pq.push({x-1,y});
}else{
if(a[idx].first!=lst) cout << a[idx].first << " " << a[idx].second << "\n",lst=a[idx--].second;
else cout << a[idx].second << " " << a[idx].first << "\n",lst=a[idx--].first;
}
for(auto [x,y]:ve) pq.push({x,y});
}
for(int i=1;i<=idx;i++){
if(a[i].first!=lst) cout << a[i].first << " " << a[i].second << "\n",lst=a[i].second;
else cout << a[i].second << " " << a[i].first << "\n",lst=a[i].first;
}
}
星期二:
hdoj 4825 01trie hdoj传送门
思路:在01字典树上贪心,此处01字典树和字典树没什么区别,就是二维大小为2
代码如下:
const int N=3.2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int ch[N][2],idx;
ll num[N];
void insert(ll n){
int p=0;
for(int i=31;i>=0;i--){
int b=n>>i&1;
if(!ch[p][b]) ch[p][b]=++idx;
p=ch[p][b];
}
num[p]=n;
}
ll fnd(ll x){
int p=0;
for(int i=31;i>=0;i--){
int b=x>>i&1;
if(ch[p][b^1]) p=ch[p][b^1];
else p=ch[p][b];
}
return num[p];
}
void solve(){
int tc; cin >> tc;
for(int ii=1;ii<=tc;ii++){
memset(ch,0,sizeof ch);
idx=0;
cout << "Case #" << ii << ":\n";
int q; cin >> n >> q;
for(int i=1;i<=n;i++){
ll a; cin >> a;
insert(a);
}
while(q--){
ll s; cin >> s;
cout << fnd(s) << "\n";
}
}
}
洛谷 P4551 洛谷传送门
思路:树上任意两点 i,j异或路径等价于从1到 i异或路径 ^ 从1到 j异或路径,由异或原理易证
dfs时把从 1到所有点异或路径值存入 trie,然后遍历,对每个值在树上贪心找一遍,ans取max
代码如下:
const int N=3e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<PII>ve[N];
vector<int>ve2;
int ch[N][2],idx;
int num[N];
void insert(int x){
int p=0;
for(int i=31;i>=0;i--){
int b=x>>i&1;
if(!ch[p][b]) ch[p][b]=++idx;
p=ch[p][b];
}
num[p]=x;
}
int fnd(int x){
int p=0;
for(int i=31;i>=0;i--){
int b=x>>i&1;
if(ch[p][b^1]) p=ch[p][b^1];
else p=ch[p][b];
}
return x^num[p];
}
void dfs(int x,int f,int num){
for(auto [w,v]:ve[x]) if(v!=f){
ve2.push_back(num^w);
insert(num^w);
dfs(v,x,num^w);
}
}
void solve(){
cin >> n;
for(int i=1;i<n;i++){
int u,v,w; cin >> u >> v >> w;
ve[u].push_back({w,v});
ve[v].push_back({w,u});
}
dfs(1,0,0);
int ans=0;
for(int i:ve2) ans=max({fnd(i),i,ans});
cout << ans;
}
cf testing round 19 C2 cf传送门
思路:哈希板子题,但若用ull会出现冲突,我是在wa了后改成双哈希,实测只用base=13331和mod=1e9+7的单哈希也不会wa,看来ull自动取模还是不太稳健
代码如下:
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
const int base1=131,base2=13331;
ull ha1[N],p1[N];
ll ha2[N],p2[N];
void solve(){
string s; cin >> s;
n=s.size(); s=" "+s;
p1[0]=p2[0]=1;
for(int i=1;i<=n;i++){
p1[i]=p1[i-1]*base1;
ha1[i]=ha1[i-1]*base1+s[i];
p2[i]=p2[i-1]*base2%mod;
ha2[i]=(ha2[i-1]*base2+s[i])%mod;
}
for(int i=n/2+1;i<n;i++){
ull hash1=ha1[n]-ha1[n-i]*p1[i];
ll hash2=(ha2[n]-ha2[n-i]*p2[i]%mod+mod)%mod;
if(ha1[i]==hash1 && ha2[i]==hash2){cout << "YES\n" << s.substr(1,i); return ;}
// if(ha2[i]==hash2){cout << "YES\n" << s.substr(1,i); return ;} //实测可用
}
cout << "NO";
}
星期三:
cf round 970 div3 G gcd cf传送门
思路:注意这个操作能使所有数变为全部的gcd,那么贪心地放a数组应为0, gcd, 1*gcd, 2*gcd ...然后mex可O(1)求,但我用了个二分
代码如下:
ll n;
void solve(){
ll k; cin >> n >> k;
ll gc=0;
for(int i=1;i<=n;i++){
ll a; cin >> a;
gc=__gcd(gc,a);
}
if(n==1){
if(gc>=k) cout << k-1 << "\n";
else cout << k << "\n";
return ;
}
if(gc==1) cout << n-1+k << "\n";
else{
ll l=k,r=1e9+2e5,res=0;
while(l<=r){
ll mid=l+r>>1;
if(min((mid-1+gc-1)/gc,n)+k>=mid) res=mid,l=mid+1;
else r=mid-1;
}
cout << res-1 << "\n";
}
}
cf round 970 div3 H cf传送门
思路:首先想到对于所有>=x的值都将其取模,那么对于数num,范围 k*x - k*x+num ( k=0,1,2,3...的数都会小于等于 num,此范围内的数可以用桶数组+前缀和快速求得
按照题目给出的中位数的定义,需要n个数中除去自己后>=n/2个数,二分中位数,枚举范围进行check,复杂度方面,先不看二分,为调和级数的复杂度,再加个二分即 O(n*log^2)
代码如下:
onst int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int b[N],ans[N];
void solve(){
int q; cin >> n >> q;
for(int i=1;i<=n;i++) b[i]=0;
for(int i=1;i<=n;i++){
int a; cin >> a;
b[a]++;
}
for(int i=1;i<=n;i++) b[i]+=b[i-1];
for(int x=1;x<=n;x++){
int l=0,r=x,res=0;
while(l<=r){
int mid=l+r>>1;
ll sum=b[mid];
for(int k=1;k*x<=n;k++) sum+=b[min(1ll*k*x+mid,n)]-b[k*x-1];
if(sum-1>=n/2) res=mid,r=mid-1; //注意和n取min,以免越界
else l=mid+1;
}
ans[x]=res;
}
while(q--){
int x; cin >> x;
cout << ans[x] << " ";
}
cout << "\n";
}
cf round 969 div2 B cf传送门
思路:简单题,只是觉得有点意思,刚开始还想到了动态开点权值线段树
其实因为操作很弱,只需一个变量维护最大值就可以了
代码如下:
ll n;
void solve(){
int m; cin >> n >> m;
ll ma=0;
for(int i=1;i<=n;i++){
int a; cin >> a;
ma=max(1ll*a,ma);
}
while(m--){
char op;int l,r; cin >> op >> l >> r;
if(l<=ma && r>=ma) op=='+'?ma++:ma--;
cout << ma << " ";
}
cout << "\n";
}
星期四:
cf round 971 div4 G1 cf传送门
思路:对一段序列修改最少值让序列满足逐一加一,可使 a【i】-= i,就转化为找一段序列中出现最多的值,可用值域线段树,但其实没必要,一个map和multiset也可实现动态维护
代码如下:
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
int ans[N];
void solve(){
int k,q; cin >> n >> k >> q;
map<int,int>mp,vi;
multiset<int>mt;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]-=i;
}
int ma=0;
for(int i=1;i<=k;i++) ma=max(++mp[a[i]],ma);
ans[1]=k-ma;
for(int i=1;i<=n;i++) if(!vi[a[i]]) mt.insert(mp[a[i]]),vi[a[i]]=1;
for(int i=2;i+k-1<=n;i++){
mt.erase(mt.find(mp[a[i-1]]));
mt.insert(--mp[a[i-1]]);
mt.erase(mt.find(mp[a[i+k-1]]));
mt.insert(++mp[a[i+k-1]]);
ans[i]=k-*(--mt.end());
}
while(q--){
int l,r; cin >> l >> r;
cout << ans[l] << "\n";
}
}
星期五:
cf round 971 div4 G2 cf传送门
思路:此题的c【i】为G1中的ans【i】,f 和 g为倍增数组
p【i】为 i右边离 i最近且 c【p【i】】< c【i】的点,可单调栈处理,记 i到 p【i】为一步,这一步的贡献即为f【i】【0】= (p【i】- i )* c【i】,可知从 i到 p【i】-1的点的贡献都为 c【i】,那么计算 l到r-k+1的总贡献即可用倍增
注意倍增时不要超出 r-k+1 的范围
代码如下:
const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
int c[N];
ll f[N][20],g[N][20];
void solve(){
int k,q; cin >> n >> k >> q;
map<int,int>mp,vi;
multiset<int>mt;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]-=i;
}
int ma=0;
for(int i=1;i<=k;i++) ma=max(++mp[a[i]],ma);
c[1]=k-ma;
for(int i=1;i<=n;i++) if(!vi[a[i]]) mt.insert(mp[a[i]]),vi[a[i]]=1;
for(int i=2;i+k-1<=n;i++){
mt.erase(mt.find(mp[a[i-1]]));
mt.insert(--mp[a[i-1]]);
mt.erase(mt.find(mp[a[i+k-1]]));
mt.insert(++mp[a[i+k-1]]);
c[i]=k-*(--mt.end());
}
stack<int>sk;
for(int i=n;i>n-k+1;i--) g[i][0]=n;
for(int i=n-k+1;i;i--){
while(!sk.empty() && c[sk.top()]>=c[i]) sk.pop();
if(sk.empty()) g[i][0]=n;
else g[i][0]=sk.top();
sk.push(i);
f[i][0]=1ll*(g[i][0]-i)*c[i];
}
for(int j=1;j<20;j++){
for(int i=1;i<=n;i++){
f[i][j]=f[i][j-1]+f[g[i][j-1]][j-1];
g[i][j]=g[g[i][j-1]][j-1];
}
}
while(q--){
int l,r; cin >> l >> r; r-=k-1;
ll pos=l,ans=0;
for(int j=19;j>=0;j--) if(g[pos][j]<=r){ //pos必须<=r
ans+=f[pos][j];
pos=g[pos][j];
}
ans+=(r-pos+1ll)*c[pos];
// cout << l << " " << r << "\n";
cout << ans << "\n";
}
}
cf round 969 div2 C cf传送门
思路:首先易想到操作等价于使任意数+=或-=gcd(a,b),那么答案一定小于gcd
先让所有数对gcd取模,排序后答案取 c【n】- c【1】,再遍历一遍,使 c【i-1】+= gc成为最大值,和此时最小值 c【i】作差,ans取min
代码如下:
const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
ll c[N];
void solve(){
ll a,b; cin >> n >> a >> b;
ll gc=__gcd(a,b);
for(int i=1;i<=n;i++){
cin >> c[i];
c[i]%=gc;
}
sort(c+1,c+n+1);
ll ans=c[n]-c[1];
for(int i=2;i<=n;i++) ans=min(c[i-1]+gc-c[i],ans);
cout << ans << "\n";
}
补 24牛客多校 四 I 牛客传送门
唉唉,还是太菜
思路:注意到若 【l,r】满足完全区间,则【l+1,r】也一定是完全区间,换句话说 l增加时,r 一定不减,由此可以想到双指针,对完全区间【l,r】,判断【l,r+1】是否完全即判断 r+1是否和 l 到 r每个点都连了边,可以对右端点存左端点,然后二分来实现判断
代码如下:
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
vector<int>ve[N];
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v; cin >> u >> v;
ve[v].push_back(u);
}
ll ans=0;
for(int i=2;i<=n;i++) sort(ve[i].begin(),ve[i].end()); //别漏了排序
for(int i=1,j=1;i<=n;i++){
j=max(i,j);
while(j<n && ve[j+1].end()-lower_bound(ve[j+1].begin(),ve[j+1].end(),i)==j-i+1) j++;
ans+=j-i+1;
}
cout << ans;
}
补 24牛客多校 四 C 牛客传送门
思路:看到排列首先考虑处理出环。发现操作等价于将环长度减3,最后一步可以减4,若环长度%3为1或为0都直接通过减3减4消除,若%3为2,注意操作可以一次处理两个长为2的环,所以统计cnt,最后加上 cnt/2 向上取整
代码如下:
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int p[N];
bool vi[N];
int len;
void dfs(int x){
if(vi[x]) return ;
vi[x]=1;
len++;
dfs(p[x]);
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> p[i];
vi[i]=0;
}
int cnt2=0,ans=0;
for(int i=1;i<=n;i++) if(!vi[i] && p[i]!=i){
len=0;
dfs(i);
ans+=len/3;
if(len%3==2) cnt2++;
}
ans+=(cnt2+1)/2;
cout << ans << "\n";
}
星期六:
补 24牛客多校4 H 牛客传送门
思路: 抽象的操作,近似于猜结论题,但雨巨讲的非常好
代码如下:
const int N=2e6+10;
const int mod=1e9+7;
ll n;
ll a[N];
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
sort(a+1,a+n+1);
ll gc=0;
for(int i=1;i<n;i++) gc=__gcd(a[i+1]-a[i],gc);
cout << gc << "\n";
}
周日:
CCPC网络赛打的是依托, 打到中途和铭轮流睡大觉有点蚌埠住
补24 牛客多校4 A 牛客传送门
思路: 注意读题,题面给了几个关键信息,一是给边形式为a,b且a是b的父亲,代表着不用双边建树,二更为关键,询问的c不会是之前的b,代表着c必定是一棵树的根,因为没当过儿子
虽是第一次见,但应该说是比较典的带权并查集,可以维护一棵树的深度,dep【i】代表其到 fa【i】的距离,输入a,b时将dep【b】初始化为1,修改下fnd函数,使其在找祖宗时dep也会变,然后在连边时,对 a的祖宗an为根的树的深度取个max
说下改fnd函数时写错的地方,第一次写把对于dep的处理写为了dep[x]=dep[fa[x]]+1,实际应为dep[x]+=dep[fa[x]],为什么+1错了呢。举个栗子,一长为2的链,根节点为a,叶子为b,此时b的深度即dep【b】为2,fa【b】是a,如果输入an,a,将a作为an的儿子,那么fa【a】=an,a的深度变为dep【a】=1,在压缩时,如果写的+1,那么dep【b】=dep【a】+1 =2,就会出错,,所以对深度的处理应为+=dep【fa【x】】
代码如下:
const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
int fa[N];
int dep[N],ans[N];
int fnd(int x){
if(fa[x]==x) return x;
int an=fnd(fa[x]);
dep[x]+=dep[fa[x]]; //注意此处应为+=
return fa[x]=an;
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) fa[i]=i,ans[i]=dep[i]=0;
for(int i=1;i<n;i++){
int a,b,c; cin >> a >> b >> c;
int rt=fnd(a);
ans[rt]=max(ans[b]+dep[a]+1,ans[rt]);
fa[b]=a,dep[b]=1; //初始化
cout << ans[c] << " \n"[i==n-1];
}
}