星期一:
补24河南萌新 四 G 牛客传送门
线段树维护字符串哈希,不错,值得一写
思路:可以想到1,2询问其实是一样的,正着反着循环节的长度都不会改变
如何快速找出循环节呢,首先如果在 l-r区间检查 len是否为循环节,一节一节查哈希值是明显不可行的,可查 [ l, r-len ]和 [ l+len,r ]的哈希值是否相等,就能大大减少查询次数
然后是如何枚举 len,如果从小到大枚举因子,复杂度为根号级别,此题中是行不通的。有一个方法是从大到小枚举 len,如果 len不行,那么 len的所有因子肯定也不是循环节,把所有因子标记上,以后枚举到时就可跳过,这样会比根号快很多
以上算是两个技巧,可以记着
代码如下:
const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
const int ba=131;
ull hsh_tb[N][27],qp[N];
vector<int>fact[N];
void init(){
qp[0]=1;
for(int i=1;i<N;i++){
qp[i]=qp[i-1]*ba;
for(int j=1;j<=26;j++) hsh_tb[i][j]=hsh_tb[i-1][j]*ba+j;
} //预处理哈希值和快速幂
for(int i=1;i<N;i++)
for(int j=i*2;j<N;j+=i) fact[j].push_back(i); //预处理因子
}
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
ll l,r;
ull hsh1;
int tag;
ll len;
}t[N<<2];
ll ql,qr,qv;
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.hsh1=a.hsh1*qp[b.len]+b.hsh1;
res.tag=0;
res.len=a.len+b.len;
return res;
}
void pushup(int p){t[p]=merge(t[lc],t[rc]);}
void bd(int p,int l,int r){
t[p]={l,r,0,0,1};
if(l==r){
char c; cin >> c;
t[p].hsh1=c-'a'+1;
return ;
}
int mid=l+r>>1;
bd(lc,l,mid);
bd(rc,mid+1,r);
pushup(p);
}
void pushdn(int p){
if(!t[p].tag) return ;
t[lc].hsh1=hsh_tb[t[lc].len][t[p].tag];
t[lc].tag=t[p].tag;
t[rc].hsh1=hsh_tb[t[rc].len][t[p].tag];
t[rc].tag=t[p].tag;
t[p].tag=0;
}
void update(int p){
if(ql<=t[p].l && qr>=t[p].r){
t[p].hsh1=hsh_tb[t[p].len][qv];
t[p].tag=qv;
return ;
}
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(ql<=mid) update(lc);
if(qr>mid) update(rc);
pushup(p);
}
void updt(int l,int r,char c){
ql=l,qr=r;
qv=c-'a'+1;
update(1);
}
nod query(int p){
if(ql<=t[p].l && qr>=t[p].r) return t[p];
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(ql>mid) return query(rc);
if(qr<=mid) return query(lc);
return merge(query(lc),query(rc));
}
ull ask_hsh1(int l,int r){
ql=l,qr=r;
return query(1).hsh1;
}
}tr;
bool check1(int l,int r,int len){
return tr.ask_hsh1(l,r-len)==tr.ask_hsh1(l+len,r);
}
void solve(){
init();
int q; cin >> n >> q;
tr.bd(1,1,n);
while(q--){
int op,l,r; cin >> op >> l >> r;
if(!op){
char c; cin >> c;
tr.updt(l,r,c);
}else if(op==1){
// __gnu_pbds::gp_hash_table<int,int>st; //和set用法差不多的容器
set<int>st;
int len=r-l+1,ans=len;
// for(int i:fact[len]) if(check1(l,r,i)){ans=i; break;} //根号复杂度,不可接受
for(int i=fact[len].size()-1;i>=0;i--){
if(st.find(fact[len][i])!=st.end()) continue;
if(check1(l,r,fact[len][i])) ans=fact[len][i];
else for(int ff:fact[fact[len][i]]) st.insert(ff);
}
cout << ans << "\n";
}else{
// __gnu_pbds::gp_hash_table<int,int>st;
set<int>st;
int len=r-l+1,ans=len;
// for(int i:fact[len]) if(check2(l,r,i)){ans=i; break;}
for(int i=fact[len].size()-1;i>=0;i--){
if(st.find(fact[len][i])!=st.end()) continue;
if(check1(l,r,fact[len][i])) ans=fact[len][i];
else for(int ff:fact[fact[len][i]]) st.insert(ff);
}
cout << ans << "\n";
}
}
}
ABC365 F的官方题解真抽象,atcoder专用头文件里封装的线段树,配上全英文题解,看一晚上加一早上看不明白,择日再战
星期二:
重做 24牛客多校1 A 牛客传送门
组合数好题,题解在8.4里
板刷easy数据结构 牛客传送门
题意:一排列,从2到n给出n-1个数,代表在 p【i】前有多少数比 p【i】小,求原排列
思路:注意数据范围,想到应该在 n^2左右。注意到最后一个数是可以最先得知的,即 a【i】+1,这题样例很好造,多造俩样例再观察下规律,就发现可以给已经填过的数加上1的权值,p【i】 不能一下得到答案,需要枚举check,若枚举到 j,查询在 p【i】后填过多少比 j小的数 (树状数组,a【i】代表在 p【i】前有多少小于等于 j的数 (经过++处理,若加起来后等于 j即满足,那么此数一定就是 j,复杂度感觉有点极限
代码如下:
ll n;
int t[N],a[N];
int ans[N];
bool vi[N];
int lowbit(int x){return x&-x;}
void add(int x){
for(int i=x;i<=8000;i+=lowbit(i)) t[i]++;
}
int ask(int x){
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
void solve(){
cin >> n;
for(int i=2;i<=n;i++){
cin >> a[i];
a[i]++;
}
for(int i=n;i>1;i--){
for(int j=1;j<=n;j++)
if(ask(j)+a[i]==j){ans[i]=j; break;}
add(ans[i]);
vi[ans[i]]=1;
}
for(int i=1;i<=n;i++) if(!vi[i]){ans[1]=i; break;}
for(int i=1;i<=n;i++) cout << ans[i] << "\n";
}
晚 cf round 966 div3, 又是警钟长鸣的一场
贴 F cf传送门
思路:贪了两次,一次wa2,一次过不了样例,转写dp
dp[ i ][ j ]表示考虑到第 i个矩形, 拿 j分的最小操作数, 很朴素的转移就 ok
贴这题的原因是dp转移时, 又忘记和自己取 min了,上次还说事不过三...没想到没多久又被拿下了,并付出了 cf上蓝的代价。。。多么痛的领悟
由此, 警钟长鸣
! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
代码如下:
ll n;
PII a[1010];
int b[1010][210]; //表示第 i个矩形拿 j分的最小操作数
int dp[1010][210];
ll k;
inline void op(int i,int n,int m,ll ne,ll sc){
if(sc>k) return ;
if(n>m) swap(n,m);
if(ne<=0) return ;
if(n==1 && m==1){
// return op(n,m-1,ne-2,sc+2)+1;
b[i][sc+2]=b[i][sc]+1;
b[i][sc+1]=1e9;
op(i,n,m-1,ne-2,sc+2);
}else{
// return op(n,m-1,ne-1,sc+1)+n;
b[i][sc+1]=b[i][sc]+n;
op(i,n,m-1,ne-1,sc+1);
}
}
void solve(){
cin >> n >> k;
ll sum=0;
for(int i=1;i<=n;i++){
cin >> a[i].first >> a[i].second;
sum+=a[i].first+a[i].second;
if(a[i].first>a[i].second) swap(a[i].first,a[i].second);
}
if(sum<k){cout << "-1\n"; return ;}
for(int i=1;i<=n;i++){
memset(b[i],0x3f,sizeof b[i]);
b[i][0]=0;
op(i,a[i].first,a[i].second,a[i].first+a[i].second,0);
}
memset(dp,0x3f,sizeof dp);
dp[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=k+1;j++){
for(int y=0;y<=j;y++){
dp[i][j]=min({dp[i-1][j],dp[i-1][j-y]+b[i][y],dp[i][j]});
//一定要和自己取min口牙 ! ! ! !
// if(i==2 && j==4 && y==2){
// cout << dp[i-1][j-y] << "\n";
// cout << b[i][y] << "\n";
// cout << dp[i-1][j] << "\n";
// cout << dp[i][j] << "\n";
// }
}
}
}
cout << min(dp[n][k],dp[n][k+1]) << "\n";
// cout << dp[2][4];
}
星期三:
下午 24河南萌新 五 题目总的来说比较水
贴道暴搜博弈 牛客传送门
因为题意过典,产出大量O(1)判断的规律大神,是谁我不说(
思路:第一次用 dfs写博弈,效果不错,主要是这题对于日期的处理比较繁琐
如果是A操作,那么所有策略中有一个必胜就是必胜,res取 |,如果是B那么所有策略都必须输,res取 &,再加个记忆化是好习惯
代码如下:
bool ifr(int y){ //判断是否闰年
y+=2000;
return (y%400==0)||(y%4==0 && y%100);
}
int md[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool ifok(int y,int m,int d){ //判断日期是否合法
if(y>24) return 0;
if(m>12) return 0;
int okd=md[m]; if(ifr(y) && m==2) okd--;
if(d>okd) return 0;
if(y==24 && m==8 && d>1) return 0;
return 1;
}
int dp[25][13][33][2];
int nextm(int y,int m,int d){ //给出下一个月的日期
if(ifok(y,m+1,d)) return y*10000+(m+1)*100+d;
if(m==12 && ifok(y+1,1,d)) return (y+1)*10000+100+d;
return 0;
}
int nextd(int y,int m,int d){ //给出下一天的日期
int ny=y,nm=m,nd=d;
if(ifok(ny,nm,nd+1)) return ny*10000+nm*100+nd+1;
int ld=md[m]; if(ifr(y) && m==2) ld--;
if(d==ld && ifok(ny,nm+1,1)) return ny*10000+(nm+1)*100+1;
if(d==ld && m==12 && ifok(ny+1,1,1)) return (ny+1)*10000+100+1;
return 0;
}
bool dfs(int y,int m,int d,bool ifa){
if(y==24 && m==8 && d==1) return !ifa;
if(dp[y][m][d][ifa]!=-1) return dp[y][m][d][ifa];
bool res;
if(ifa){
res=0;
int nm=nextm(y,m,d),nd=nextd(y,m,d);
if(nm) res|=dfs(nm/10000,nm%10000/100,nm%100,0);
// cout << nm/10000 << " " << nm%10000/100 << " " << nm%100 << "\n";
if(nd) res|=dfs(nd/10000,nd%10000/100,nd%100,0);
// cout << nd/10000 << " " << nd%10000/100 << " " << nd%100 << "\n";
}else{
res=1;
int nm=nextm(y,m,d),nd=nextd(y,m,d);
if(nm) res&=dfs(nm/10000,nm%10000/100,nm%100,1);
if(nd) res&=dfs(nd/10000,nd%10000/100,nd%100,1);
}
return dp[y][m][d][ifa]=res;
}
void solve(){
int t; cin >> t;
memset(dp,-1,sizeof dp);
while(t--){
int y,m,d; cin >> y >> m >> d; y-=2000;
if(dfs(y,m,d,1)) cout << "YES\n";
else cout << "NO\n";
}
}
补 C,赛时在洛谷找到写过的原题,直接粘的码。。
思路:赛时写的二分检查,会wa,赛后用对拍器才找到错误原因
二分时,如果枚举 i顺着放,冲突关系没有经过排序处理,会导致前面放的很随意,以至于后面遇到无法避免的大冲突,但其实是可以避免的
正解应该是按冲突从大到小排序,优先避免大冲突,直至遇到冲突无法避免,使用并查集解决
星期四:
牛客多校10,又是签到打卡
贴道 概率签到 H 牛客传送门
思路:手搓几个样例,会发现 a,b的胜利期望就是 a/(a+b),b/(a+b)
代码如下:
const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
ll qpow(ll a,ll n){
ll res=1;
while(n){
if(n&1) (res*=a)%=mod;
(a*=a)%=mod;
n>>=1;
}
return res;
}
void solve(){
ll a,b; cin >> a >> b;
ll qp=qpow(a+b,mod-2);
cout << a*qp%mod << " " << b*qp%mod;
}
cf edu round169 div2,11点半交了第一发,D wa2,然后狼狈地补了前三道,难蚌
星期五:
泻药,人在北京,刚下飞机
星期六:
补 cf edu round169 div2 D cf传送门
思路: 如果有相同颜色,那么可直通. 如果无相同颜色比如红蓝和黄绿,那么可以注意到,除了红蓝和黄绿两种城市外,其余所有城市都可以当这俩城市的中转点,并且只用中转一次,二分找下标即可
wa的原因是在 二分找 >x的城市下标后,如果没找到直接 continue掉了,而没有注意到后面还有个找 <x的下标的操作, 属于是粗糙了
代码如下:
const int N=2e6+10;
const int mod=1e9+7;
ll n;
int a[N];
vector<int>ve[22];
inline bool ifs(int a,int b){
for(int i=0;i<4;i++) if((a&1<<i)==(b&1<<i)) return 1;
return 0;
}
void solve(){
int q; cin >> n >> q;
for(int i=1;i<=12;i++) ve[i].clear();
for(int i=1;i<=n;i++){
a[i]=0;
string s; cin >> s;
if(s[0]=='R' || s[1]=='R') a[i]+=8;
if(s[0]=='B' || s[1]=='B') a[i]+=4;
if(s[0]=='G' || s[1]=='G') a[i]+=2;
if(s[0]=='Y' || s[1]=='Y') a[i]+=1;
ve[a[i]].push_back(i);
}
while(q--){
int x,y; cin >> x >> y;
if(x>y) swap(x,y);
if(ifs(a[x],a[y])){cout << y-x << "\n"; continue;}
int ans=1e9;
for(int i=3;i<=12;i++) if(i!=a[x] && i!=a[y]){
auto it=lower_bound(ve[i].begin(),ve[i].end(),x);
if(it!=ve[i].end()) ans=min(abs(*it-x)+abs(*it-y),ans);
if(it!=ve[i].begin()){ //因为continue早了没有实现这一步
auto it2=--it;
ans=min(abs(*it2-x)+abs(*it2-y),ans);
}
}
if(ans==1e9) cout << "-1\n";
else cout << ans << "\n";
}
}
补 cf round 966 div3 G cf传送门
思路: 二分出发时间,然后跑变种 dij, 看似 t1-t2这个条件有点刁,实则只需在 dij里讨论一下
如果 d已经>=t2, 那么就可以用 l1来更新 dis[v], 或者如果 d+l1 <= t1也能用 l1,否则只能用 l2更新,不过这里还需要讨论一下,若等到 t2再用 l1更快,即 t2+l1 < d+l2,则用 t2+l1更新dis[v]
代码如下:
const int N=2e5+10;
const int mod=1e9+7;
ll n;
struct nod{
int l1,l2,v;
};
vector<nod>ve[N];
ll t0,t1,t2;
ll dis[N];
bool vi[N];
inline bool check(int mid){
priority_queue<PII,vector<PII>,greater<>>pq;
// memset(dis,0x3f,sizeof dis);
// memset(vi,0,sizeof vi); //注意样例数
for(int i=1;i<=n;i++) dis[i]=1e18,vi[i]=0;
dis[1]=mid;
pq.push({mid,1});
while(!pq.empty()){
auto [d,u]=pq.top(); pq.pop();
if(vi[u]) continue;
vi[u]=1;
if(u==n) break;
for(auto [l1,l2,v]:ve[u]) if(dis[v]>d+l1){
if(d>=t2 && dis[v]>d+l1){ //需要根据d分类讨论
dis[v]=d+l1;
pq.push({dis[v],v});
}else if(d<t1){
if(d+l1<=t1 && dis[v]>d+l1){
dis[v]=d+l1;
pq.push({dis[v],v});
}else if(dis[v]>min(d+l2,t2+l1)){
dis[v]=min(d+l2,t2+l1);
pq.push({dis[v],v});
}
}else if(dis[v]>min(d+l2,t2+l1)){
dis[v]=min(d+l2,t2+l1);
pq.push({dis[v],v});
}
}
}
return dis[n]<=t0;
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=n;i++) ve[i].clear();
cin >> t0 >> t1 >> t2;
for(int i=1;i<=m;i++){
int u,v; cin >> u >> v;
int l1,l2; cin >> l1 >> l2;
ve[u].push_back({l1,l2,v});
ve[v].push_back({l1,l2,u});
}
ll l=0,r=t0,res=-1;
while(l<=r){
ll mid=l+r>>1;
if(check(mid)) res=mid,l=mid+1;
else r=mid-1;
}
cout << res << "\n";
}
周日:
百度之星决赛, 硬实力不足导致打铁
补 cf round 966 div3 H cf传送门
思路: 简单线段树题, 在权值线段树中找第一段空白长度>= k的数, 维护4e6加上二分+线段树也能过但能优化为维护 2e6值域且线段树上二分
代码如下:
const int N=2e6+10;
const int mod=1e9+7;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
int len;
int sum,tag;
int mal,mar,ma;
}t[N<<2];
ll ql,qr,qv;
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.len=a.len+b.len;
res.sum=a.sum+b.sum;
res.mal=a.mal==a.len?a.len+b.mal:a.mal;
res.mar=b.mar==b.len?a.mar+b.len:b.mar;
res.ma=max({a.ma,b.ma,a.mar+b.mal});
return res;
}
void pushup(int p){t[p]=merge(t[lc],t[rc]);}
void pushdn(int p){
if(!t[p].tag) return ;
t[lc].sum=0;
t[lc].mal=t[lc].mar=t[lc].ma=t[lc].len;
t[lc].tag=1;
t[rc].sum=0;
t[rc].mal=t[rc].mar=t[rc].ma=t[rc].len;
t[rc].tag=1;
t[p].tag=0;
}
void bd(int p,int l,int r){
t[p]={l,r,1,0,0,0,0};
if(l==r){
t[p].mal=t[p].mar=t[p].ma=1;
return ;
}
int mid=l+r>>1;
bd(lc,l,mid);
bd(rc,mid+1,r);
pushup(p);
}
void update(int p){
if(ql<=t[p].l && qr>=t[p].r){
t[p].sum+=qv;
if(t[p].sum) t[p].mal=t[p].mar=t[p].ma=0;
else t[p].mal=t[p].mar=t[p].ma=1;
return ;
}
pushdn(p);
int mid=t[p].l+t[p].r>>1;
if(ql<=mid) update(lc);
if(qr>mid) update(rc);
pushup(p);
}
void updt(int l,int r,int v){
ql=l,qr=r;
qv=v;
update(1);
}
nod query(int p){
if(ql<=t[p].l && qr>=t[p].r) return t[p];
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(ql>mid) return query(rc);
if(qr<=mid) return query(lc);
return merge(query(lc),query(rc));
}
int ask(int l,int r){
ql=l,qr=r;
return query(1).ma;
}
int fnd(int p,int k){
if(t[p].l==t[p].r) return t[p].l+k-1;
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(t[lc].ma>=k) return fnd(lc,k);
if(t[lc].mar+t[rc].mal>=k) return mid-t[lc].mar+1; //考虑两段拼接
// return fnd(rc,k); //维护值域为4e6时
if(t[rc].ma>=k) return fnd(rc,k);
else return t[rc].r-t[rc].mar+1; //维护值域为2e6时
}
}tr;
void solve(){
tr.bd(1,1,2e6);
int tc; cin >> tc;
while(tc--){
tr.t[1].sum=0;
tr.t[1].ma=tr.t[1].mal=tr.t[1].mar=tr.t[1].len;
tr.t[1].tag=1; //值域清零
cin >> n;
for(int i=1;i<=n;i++){
int a; cin >> a;
tr.updt(a,a,1);
}
int q; cin >> q;
while(q--){
char op; int x; cin >> op >> x;
if(op=='+') tr.updt(x,x,1);
if(op=='-') tr.updt(x,x,-1);
if(op=='?'){
// int l=1,r=4e6,res=0;
// while(l<=r){
// int mid=l+r>>1;
// if(tr.ask(1,mid)>=x) res=mid,r=mid-1;
// else l=mid+1;
// }
// cout << res-x+1 << " "; //log^2
cout << tr.fnd(1,x) << " "; //log
}
}
cout << "\n";
}
}