上周六:
算是暑假训练第一天,期末考完了真好
cf round951 div2 D cf传送门
之前用正解补过,又臭又长,写完就跑了,这次用哈希补一发
思路:目标字符串就两种可能,0开头和1开头,所以可直接算出目标子串的hash值,为num
因为是双哈希,所以东西较多,ha11,第一个1表示正着hash或反着hash,第二个1表示第一套base和mod或第二套,num01,第一个0表示0或1开头,第二个1表示第一套或第二套base,mod
遍历答案,例如字符串长为9,123456789,i为3,那么应该变为 456789 321,用处理出来的hash数组拼凑出来,然后和 num值进行比较即可
代码如下:
cpp
const int N=2e5+10;
const int mod1=1e9+7,mod2=998244353;
ll n;
ll p1[N],p2[N],ha11[N],ha12[N],ha21[N],ha22[N];
const int ba1=13331,ba2=131;
void solve(){
int k; cin >> n >> k;
string s; cin >> s; s=" "+s;
ll num01=0,num02=0,num11=0,num12=0;
p1[0]=p2[0]=1;
for(int i=1;i<=n;i++){
p1[i]=p1[i-1]*ba1%mod1;
p2[i]=p2[i-1]*ba2%mod2;
ha11[i]=(ha11[i-1]*ba1+s[i])%mod1;
ha12[i]=(ha12[i-1]*ba2+s[i])%mod2;
if((i-1)/k&1){
num01=(num01*ba1+'1')%mod1;
num02=(num02*ba2+'1')%mod2;
num11=(num11*ba1+'0')%mod1;
num12=(num12*ba2+'0')%mod2;
}else{
num01=(num01*ba1+'0')%mod1;
num02=(num02*ba2+'0')%mod2;
num11=(num11*ba1+'1')%mod1;
num12=(num12*ba2+'1')%mod2;
}
}
ha21[n+1]=ha22[n+1]=0;
for(int i=n;i;i--){
ha21[i]=(ha21[i+1]*ba1+s[i])%mod1;
ha22[i]=(ha22[i+1]*ba2+s[i])%mod2;
}
for(int i=1;i<=n;i++){
ll ha1=(ha11[n]-ha11[i]*p1[n-i]%mod1+mod1)%mod1*p1[i]%mod1;
ll ha2=(ha12[n]-ha12[i]*p2[n-i]%mod2+mod2)%mod2*p2[i]%mod2;
ha1+=(ha21[1]-ha21[i+1]*p1[i]%mod1+mod1)%mod1,ha1%=mod1;
ha2+=(ha22[1]-ha22[i+1]*p2[i]%mod2+mod2)%mod2,ha2%=mod2;
if((ha1==num01&&ha2==num02) || (ha1==num11&&ha2==num12)){cout << i << "\n"; return ;}
}
cout << "-1\n";
}
上周日:
补cf round 944 div4 F cf传送门
思路:从1到r,二分找范围内点集的上下界,注意判断边界情况
代码如下:
cpp
ll n;
void solve(){
cin >> n;
ll ans=0;
for(int i=1;i<=n;i++){
ll l=0,r=i,res1=-1;
while(l<=r){
ll mid=l+r>>1;
ll y=mid;
double dis=sqrt(1.0*i*i+1.0*y*y); // >=r
if(dis<n) l=mid+1;
else res1=mid,r=mid-1;
}
l=0,r=i;ll res2=-1;
while(l<=r){
ll mid=l+r>>1;
ll y=mid;
double dis=sqrt(1.0*i*i+1.0*y*y); // < r+1
if(dis>=n+1) r=mid-1;
else res2=mid,l=mid+1;
}
if(res1==-1 || res2==-1) continue;
if(!res1) ans+=4,res1++;
if(res2==i) ans-=4;
ans+=(res2-res1+1)*8;
}
cout << ans << "\n";
}
板刷构造 1600 cf传送门
题意:构造排列使得任意相邻数的差值在 2-4间
思路:从奇偶性角度考虑,俩相邻奇数或偶数差值即2,先顺着放奇数,然后放一个和最大奇数匹配的偶数即 ma1-3,然后逆着放偶数,最大的差值刚好为4
代码如下:
cpp
ll n;
void solve(){
cin >> n;
if(n<4){cout << "-1\n"; return ;}
if(n==4){cout << "3 1 4 2\n"; return ;}
int ma1=n&1?n:n-1,ma0=n&1?n-1:n; //最大的奇数和偶数
int ou=ma1-3;
for(int i=1;i<=n;i+=2) cout << i << " ";
cout << ou << " ";
for(int i=ma0;i;i-=2){
if(i==ou) continue;
cout << i << " ";
}
cout << "\n";
}
周一:
上午个人赛
补ABC 131 E 构造好题 atc传送门
题意:构造一个图,n个点且满足有 k个点对最短距离为2
思路:n很小,且对边数无限制,可构造一个菊花图,此时满足距离为2的点对数为C n-1 2,即 (n-1)*(n-2)/ 2,这是最多点对的情况,若小于 k输出 -1,若大于 k,在任意两点间连接边,则这两点不再满足即点对数减一,添边直至点对==k
代码如下:
cpp
ll n;
void solve(){
int k; cin >> n >> k;
ll p=(n-1)*(n-2)/2;
if(p<k){cout << -1; return ;}
vector<int>ve[110];
ll sum=0;
for(int i=1;i<n;i++) ve[n].push_back(i),sum++;
for(int i=1;i<n && p>k;i++){
for(int j=i+1;j<n && p>k;j++){
ve[i].push_back(j);
sum++;
p--;
}
}
cout << sum << "\n";
for(int i=1;i<=n;i++)
for(auto j:ve[i])
cout << i << " " << j << "\n";
}
牛客板刷线段树 题一 牛客传送门
读错题加上脑子抽了一度卡壳,其实基础线段树题
思路:1到n天,对每个订单检查能否满足,即从 s到 t剩余教室数是否都大于 d,可维护最小值,满足后再更新 s到 t天的剩余教室数
代码如下:
cpp
const int N=2e6+10;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
ll mi,add;
}t[N];
ll ql,qr,a[N];
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.mi=min(a.mi,b.mi);
res.add=0;
return res;
}
void pushup(int p){t[p]=merge(t[lc],t[rc]);}
void pushdn(int p){
if(!t[p].add) return ;
t[lc].mi+=t[p].add;
t[rc].mi+=t[p].add;
t[lc].add+=t[p].add;
t[rc].add+=t[p].add;
t[p].add=0;
}
void bd(int p,int l,int r){
t[p]={l,r,0,0};
if(l==r){
t[p].mi=a[l];
return ;
}
int m=l+r>>1;
bd(lc,l,m);
bd(rc,m+1,r);
pushup(p);
}
void update(int p,int v){
if(ql<=t[p].l && qr>=t[p].r){
t[p].mi+=v;
t[p].add+=v;
return ;
}
int m=t[p].l+t[p].r>>1;
pushdn(p);
if(ql<=m) update(lc,v);
if(qr>m) update(rc,v);
pushup(p);
}
nod query(int p){
if(ql<=t[p].l && qr>=t[p].r) return t[p];
int m=t[p].l+t[p].r>>1;
pushdn(p);
if(ql>m) return query(rc);
if(qr<=m) return query(lc);
return merge(query(lc),query(rc));
}
void updt(int l,int r,int v){
ql=l,qr=r;
update(1,v);
}
ll ask_min(int l,int r){
ql=l,qr=r;
return query(1).mi;
}
#undef lc
#undef rc
}tr;
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=n;i++) cin >> tr.a[i];
tr.bd(1,1,n);
for(int i=1;i<=m;i++){
int d,s,t; cin >> d >> s >> t;
if(tr.ask_min(s,t)<d){cout << "-1\n" << i; return ;}
tr.updt(s,t,-d);
}
cout << 0;
}
板刷数据结构 题二 数贝壳 牛客传送门
长见识了
思路:有很多种做法,这里使用的是树状数组。首先需要把查询记录下来离线处理,按 r 排序。 对于一个区间内的相同数字,只关心最右边那个,vi记录数字的最后一次出现位置,例如 1 4 5 1,考虑前三个时,vi【1】=1,vi【4】=2,vi【5】=3,树状数组形为 1 1 1 0,然后下一个查询的 r为4,a【4】已被记录,那么就更新 vi【1】为4,树状数组形为 0 1 1 1。ask( r) - ask( l-1)即为查询的答案
代码如下:
cpp
const int N=2e6+10,M=210;
ll n;
ll t[N],a[N];
struct nod{
int l,r;
int id,ans;
}q[N];
int vi[N];
int lowbit(int x){return x&-x;}
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=v;
}
ll ask(int x){
ll res=0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
int m; cin >> m;
for(int i=1;i<=m;i++){
cin >> q[i].l >> q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,[](nod a,nod b){
return a.r<b.r;
});
for(int i=1;i<=m;i++){
for(int j=q[i-1].r+1;j<=q[i].r;j++){
if(!vi[a[j]]) add(j,1),vi[a[j]]=j;
else{
add(vi[a[j]],-1);
add(j,1);
vi[a[j]]=j;
}
}
q[i].ans=ask(q[i].r)-ask(q[i].l-1);
}
sort(q+1,q+m+1,[](nod a,nod b){
return a.id<b.id;
});
for(int i=1;i<=m;i++) cout << q[i].ans << "\n";
}
星期二:
板刷数据结构 题三
因为读错题(没注意样例,被耽误了一晚上和半个早上。。
以后读完题记得一定看样例!!!
思路:和数贝壳很像,注意这题一种采了的颜色对答案的贡献只有1! 把询问按 r大小排序,升序处理,把第一次出现的颜色标记一下,但并不赋值,此颜色第二次以及更多次出现时,才对上一次出现的位置+1,且对上上次的位置-1
代码如下:
cpp
const int N=2e6+10;
ll n;
ll a[N],t[N];
struct nod{
int l,r;
int id,ans;
}q[N];
PII vi[N];
int lowbit(int x){return x&-x;}
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=v;
}
ll ask(int x){
ll res=0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
void solve(){
int c,m; cin >> n >> c >> m;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=m;i++){
cin >> q[i].l >> q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,[](nod a,nod b){
return a.r<b.r;
});
for(int i=1;i<=m;i++){
for(int j=q[i-1].r+1;j<=q[i].r;j++){
if(!vi[a[j]].first) vi[a[j]].first=j;
else if(!vi[a[j]].second) add(vi[a[j]].first,1),vi[a[j]].second=j;
else{
add(vi[a[j]].first,-1),vi[a[j]].first=vi[a[j]].second;
add(vi[a[j]].first,1),vi[a[j]].second=j;
}
}
q[i].ans=ask(q[i].r)-ask(q[i].l-1);
}
sort(q+1,q+m+1,[](nod a,nod b){
return a.id<b.id;
});
for(int i=1;i<=m;i++) cout << q[i].ans << "\n";
}
最后半小时才知道有个训练赛
ABC 357 C atc传送门
思路:递归赋值
代码如下:
cpp
ll n;
char c[740][740];
void kk(int a,int b,int x,int y){
if(a==x){c[a][b]='#'; return ;}
int len=x-a+1;
len/=3;
for(int i=0;i<3;i++) kk(a,b+i*len,a+len-1,b+(i+1)*len-1); //前三个格子
kk(a+len,b,a+2*len-1,b+len-1);
for(int i=a+len;i<a+2*len;i++)
for(int j=b+len;j<b+2*len;j++)
c[i][j]='.';
kk(a+len,b+2*len,a+2*len-1,b+3*len-1); //中间三个格子
for(int i=0;i<3;i++) kk(a+2*len,b+i*len,a+3*len-1,b+(i+1)*len-1); //最后三个格子
}
void solve(){
cin >> n;
int x=1,y=1;
for(int i=1;i<=n;i++) x*=3,y*=3;
kk(1,1,x,y); //左上坐标为1,1,右下坐标为x,y
for(int i=1;i<=x;i++){
for(int j=1;j<=y;j++) cout << c[i][j];
cout << "\n";
}
}
补ABC 325 D atc传送门
思路:贪心,但比较麻烦。将物品开始 st和结束 ed时间作为pair数据升序排列,在所有 st<=ti的物品中,选择 ed最小的打印,用优先队列实现找最小 ed,每打印一个 ti++,时间++时注意把可开始的物品放入优先队列,若队列为空,则此时无物品可打印,将时间跳转到下一个物品的 st
代码如下:
cpp
const int N=2e6+10,M=210;
ll n;
PII a[N];
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
ll t,d; cin >> t >> d;
a[i].first=t;
a[i].second=t+d;
}
sort(a+1,a+n+1);
priority_queue<ll,vector<ll>,greater<ll>>pq;
ll ti=0,ans=0;
for(int i=1;;ti++){
if(pq.empty()){
if(i==n+1) break;
ti=a[i].first;
}
while(i<=n && a[i].first==ti) pq.push(a[i++].second);
while(!pq.empty() && pq.top()<ti) pq.pop();
if(!pq.empty()) pq.pop(),ans++;
}
cout << ans;
}
补ABC 334 F atc传送门
思路:dp【i】表示送完了前 i家并回到起点的最短距离,dis【i】为一直走到 i点距离即前缀和 ,ho【i】为与起点的距离
转移式即为:dp【i】= dp【j】+ ho【j+1】+ dis【i】- dis【j+1】+ ho【i】( j >= i-k)
转移式比较麻烦,但推出来后不难想到单调队列优化
最后注意dis【i】是从起点开始算,到 i的距离,而不是第一家小孩开始算
代码如下:
cpp
const int N=2e6+10,M=210;
ll n;
ll x[N],y[N];
double dis[N],ho[N];
double dp[N];
void solve(){
int k; cin >> n >> k;
int sx,sy; cin >> sx >> sy;
dis[1]=sqrt((x[1]-sx)*(x[1]-sx)+(y[1]-sy)*(y[1]-sy));
for(int i=1;i<=n;i++){
cin >> x[i] >> y[i];
if(i>1) dis[i]=sqrt((x[i]-x[i-1])*(x[i]-x[i-1])+(y[i]-y[i-1])*(y[i]-y[i-1]));
dis[i]+=dis[i-1];
ho[i]=sqrt((x[i]-sx)*(x[i]-sx)+(y[i]-sy)*(y[i]-sy));
}
priority_queue<pair<double,int>,vector<pair<double,int>>,greater<pair<double,int>>>pq;
dp[0]=0;
pq.push({-dis[1]+ho[1],0});
for(int i=1;i<=n;i++){
while(pq.top().second<i-k) pq.pop();
dp[i]=pq.top().first+dis[i]+ho[i];
pq.push({dp[i]-dis[i+1]+ho[i+1],i});
}
cout << fixed << setprecision(8) << dp[n];
}
补 ABC 299 E atc传送门
思路:除了必须白色的点,全涂为黑色,然后用 bfs来check是否合法,用 dfs进行check会wa, 目前不知道其原因
dfs错误的原因想到了,x和dis代表的是点以及和起点的距离,但因dfs是深度优先搜索,dis会出错
代码如下:
cpp
ll n;
vector<int>ve[2020];
int p[2020],d[2020];
string s;
bool vi[2020];
bool ifd;
bool dfs(int x,int dis){
if(dis>0 && s[x-1]=='1'){ifd=0; return 0;}
if(dis==0) return s[x-1]=='1';
vi[x]=1;
bool if1=0;
for(auto i:ve[x])
if(!vi[i]) vi[i]=1,if1|=dfs(i,dis-1);
if(!ifd) return 0;
return if1;
}
bool bfs(int x,int d){
queue<PII>q;
q.push({x,0});
vi[x]=1;
while(!q.empty()){
auto [x,y]=q.front(); q.pop();
if(y==d){
if(s[x-1]=='1') return 1;
continue;
}
for(auto i:ve[x])
if(!vi[i]) vi[i]=1,q.push({i,y+1});
}
return 0;
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
int k; cin >> k;
set<int>mtw;
for(int i=1;i<=k;i++){
cin >> p[i] >> d[i];
queue<PII>q;
memset(vi,0,sizeof vi);
q.push({p[i],0});
vi[p[i]]=1;
while(!q.empty()){
auto [x,y]=q.front(); q.pop();
if(y==d[i]) continue;
mtw.insert(x);
for(auto j:ve[x])
if(!vi[j]) vi[j]=1,q.push({j,y+1});
}
}
if(!k){cout << "Yes\n"; for(int i=1;i<=n;i++) cout << 1; return ;}
for(int i=1;i<=n;i++) s.append("0");
for(int i=1;i<=n;i++) if(mtw.find(i)==mtw.end()) s[i-1]='1';
for(int i=1;i<=k;i++){
memset(vi,0,sizeof vi);
ifd=1;
if(!bfs(p[i],d[i])){cout << "No"; return ;}
}
cout << "Yes\n" << s;
}
星期三:
补 hdu1421 smu传送门
很普通的dp,赛时没出是我的问题
思路:dp【i】【j】表示考虑到前 i个物品,选了 j对的最小疲劳
有一个比较显然的结论(但我没想到)的是,物品配对的选择一定是排序后两两相邻着拿
转移式:dp【i】【j】= min( dp【i-2】【j-1】+ ( a【i】- a【i-1】)^2,dp【i-1】【j】)
代码如下:
cpp
ll n;
int a[2020];
int dp[2020][1010];
void solve(){
int k;
while(cin >> n >> k){
for(int i=1;i<=n;i++) cin >> a[i];
sort(a+1,a+n+1);
for(int i=0;i<=n;i++)
for(int j=1;j<=k;j++) dp[i][j]=1e9;
for(int i=2;i<=n;i++){
for(int j=1;j*2<=i;j++){
dp[i][j]=min(dp[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),dp[i-1][j]);
}
}
cout << dp[n][k] << "\n";
}
}
补ABC 200 D atc传送门
之前没接触过鸽巢原理,现在遇到了一道与之相关的题,真是学到了
思路:n为200,看似很小,但如果要纯暴力又肯定不行。根据鸽巢原理,根据对200的模数把序列分类,最多需要多少序列就能确定一定有模数相同的序列呢?201个序列足矣,长为 n的序列存在 2^n-1个非空子序列,所以若n>8,也只需要对前8个进行暴力分类,就一定有解
代码如下:
cpp
ll n;
ll a[220];
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
map<int,vector<int>>mp;
int cnt=0;
vector<int>ve[220];
int len=min(8ll,n);
for(int mask=1;mask<1<<len;mask++){
ll sum=0;
vector<int>tp;
for(int j=0;j<len;j++) if(mask&1<<j) sum+=a[j+1],tp.push_back(j+1);
mp[++cnt]=tp;
ve[sum%200].push_back(cnt); //序列的编号
}
for(int i=0;i<200;i++)
if(ve[i].size()>1){
cout << "Yes\n";
cout << mp[ve[i][0]].size() << " ";
for(auto j:mp[ve[i][0]]) cout << j << " ";
cout << "\n";
cout << mp[ve[i][1]].size() << " ";
for(auto j:mp[ve[i][1]]) cout << j << " ";
return ;
}
cout << "No";
}
补ABC 204 E atc传送门
思路:dp【i】表示到达 i点的最短时间,dfs不能实现转移,需要迪杰斯特拉
dp【v】= min( t + C + D/( t+1),dp【v】),t >= dp【u】
把 t+1用 x来表示,dp【v】= x + D/x + C - 1,x >= dp【u】+1
如何求 x+D/x的最小值呢,试过三分,但整形的三分会出错,结论是 x=sqrt(D)四舍五入时最小
代码如下:
cpp
const int N=3e5+10;
ll n;
struct nod{
int v;
ll c,d;
};
vector<nod>ve[N];
ll dp[N];
bool vi[N];
void dij(){
memset(dp,0x3f,sizeof dp);
priority_queue<PII,vector<PII>,greater<PII>>pq;
pq.push({0,1});
dp[1]=0;
while(!pq.empty()){
auto [t,u]=pq.top(); pq.pop();
if(vi[u]) continue;
vi[u]=1;
for(auto nd:ve[u]){
ll v=nd.v,c=nd.c,d=nd.d;
ll x=round(sqrt(d));
x=max(t+1,x);
ll w=x+d/x+c-1;
if(w<dp[v]){
dp[v]=w;
pq.push({dp[v],v});
}
}
}
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int a,b,c,d; cin >> a >> b >> c >> d;
ve[a].push_back({b,c,d});
ve[b].push_back({a,c,d});
}
dij();
if(dp[n]>1e18) cout << -1;
else cout << dp[n];
}
补ABC 202 E atc传送门
题意:给一棵树,q次询问,每次求 u子树上有多少深度为 d的点
思路:用时间戳解决,若 v是 u的子树上的点,则满足 in【u】<= in【v】< out【v】<= out【u】,按深度来存点,在深度数组里二分查找满足条件的点集区间(只存 in【x】值
以上关于时间戳的结论在 atc此题的题解中有较详细的证明:
代码如下:
cpp
const int N=3e5+10;
ll n;
vector<int>ve[N],dep[N];
int in[N],out[N],timer;
void dfs(int x,int d){
in[x]=++timer;
dep[d].push_back(in[x]);
for(auto v:ve[x]) dfs(v,d+1);
out[x]=++timer;
}
void solve(){
cin >> n;
for(int i=2;i<=n;i++){
int p; cin >> p;
ve[p].push_back(i);
}
dfs(1,0);
int q; cin >> q;
while(q--){
int u,d; cin >> u >> d;
auto r=lower_bound(dep[d].begin(),dep[d].end(),out[u]);
auto l=lower_bound(dep[d].begin(),dep[d].end(),in[u]);
cout << r-l << "\n";
}
}
板刷数据结构 题四 数据结构 牛客传送门
思路:之前做过维护乘和加的线段树,这次多了一个平方和,需要推下式子,懒标记注意先乘再加
代码如下:
cpp
const int N=3e5+10,M=210;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
ll sum,sum2;
ll add,mul;
}t[N<<2];
int ql,qr,qop;
ll qv,a[N];
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.sum=a.sum+b.sum;
res.sum2=a.sum2+b.sum2;
res.add=0;
res.mul=1;
return res;
}
void pushup(int p){t[p]=merge(t[lc],t[rc]);}
void pushdn(int p){
if(t[p].mul!=1){
t[lc].sum2*=t[p].mul*t[p].mul;
t[rc].sum2*=t[p].mul*t[p].mul;
t[lc].sum*=t[p].mul;
t[rc].sum*=t[p].mul;
t[lc].mul*=t[p].mul;
t[rc].mul*=t[p].mul;
t[lc].add*=t[p].mul;
t[rc].add*=t[p].mul; //注意对add的影响不要遗漏
t[p].mul=1;
}
if(t[p].add){
t[lc].sum2+=2*t[lc].sum*t[p].add+(t[lc].r-t[lc].l+1)*t[p].add*t[p].add;
t[rc].sum2+=2*t[rc].sum*t[p].add+(t[rc].r-t[rc].l+1)*t[p].add*t[p].add;
t[lc].sum+=(t[lc].r-t[lc].l+1)*t[p].add;
t[rc].sum+=(t[rc].r-t[rc].l+1)*t[p].add;
t[lc].add+=t[p].add;
t[rc].add+=t[p].add;
t[p].add=0;
}
}
void bd(int p,int l,int r){
t[p]={l,r,0,0,0,1};
if(l==r){
t[p].sum=a[l];
t[p].sum2=a[l]*a[l];
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){
if(qop==1){ //区间加
t[p].sum2+=2*t[p].sum*qv+(t[p].r-t[p].l+1)*qv*qv;
t[p].sum+=(t[p].r-t[p].l+1)*qv;
t[p].add+=qv;
return ;
}else{ //区间乘
t[p].sum2*=qv*qv;
t[p].sum*=qv;
t[p].mul*=qv;
t[p].add*=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);
}
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));
}
void updt(int l,int r,int op,ll v){
ql=l,qr=r;
qop=op,qv=v;
update(1);
}
ll ask_sum(int l,int r){
ql=l,qr=r;
return query(1).sum;
}
ll ask_sum2(int l,int r){
ql=l,qr=r;
return query(1).sum2;
}
}tr;
void solve(){
int q; cin >> n >> q;
for(int i=1;i<=n;i++) cin >> tr.a[i];
tr.bd(1,1,n);
while(q--){
int op; cin >> op;
if(op==1 || op==2){
int l,r; cin >> l >> r;
if(op==1) cout << tr.ask_sum(l,r) << "\n";
if(op==2) cout << tr.ask_sum2(l,r) << "\n";
}
if(op==3 || op==4){
int l,r;ll x; cin >> l >> r >> x;
if(op==3) tr.updt(l,r,2,x);
if(op==4) tr.updt(l,r,1,x);
}
}
}
星期四:
板刷数据结构 题五 牛客传送门
思路:和上一题有点像,注意运算中时刻%P,不然会爆 ll
代码如下:
cpp
const int N=3e5+10,M=210;
ll n;
int P;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
ll sum,sum2;
ll add,mul;
}t[N<<2];
int ql,qr,qop;
ll qv,a[N];
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.sum=(a.sum+b.sum)%P;
res.sum2=(a.sum2+b.sum2+a.sum*b.sum%P)%P;
res.add=0;
res.mul=1;
return res;
}
void pushup(int p){t[p]=merge(t[lc],t[rc]);}
void pushdn(int p){
if(t[p].mul!=1){
t[lc].sum2*=t[p].mul*t[p].mul%P,t[lc].sum2%=P;
t[rc].sum2*=t[p].mul*t[p].mul%P,t[rc].sum2%=P;
t[lc].sum*=t[p].mul,t[lc].sum%=P;
t[rc].sum*=t[p].mul,t[rc].sum%=P;
t[lc].mul*=t[p].mul,t[lc].mul%=P;
t[rc].mul*=t[p].mul,t[rc].mul%=P;
t[lc].add*=t[p].mul,t[lc].add%=P;
t[rc].add*=t[p].mul,t[rc].add%=P;
t[p].mul=1;
}
if(t[p].add){
t[lc].sum2+=t[lc].sum*(t[lc].r-t[lc].l)%P*t[p].add%P+(t[lc].r-t[lc].l+1)*(t[lc].r-t[lc].l)/2*t[p].add%P*t[p].add,t[lc].sum2%=P;
t[rc].sum2+=t[rc].sum*(t[rc].r-t[rc].l)%P*t[p].add%P+(t[rc].r-t[rc].l+1)*(t[rc].r-t[rc].l)/2*t[p].add%P*t[p].add,t[rc].sum2%=P;
t[lc].sum+=(t[lc].r-t[lc].l+1)*t[p].add%P,t[lc].sum%=P;
t[rc].sum+=(t[rc].r-t[rc].l+1)*t[p].add%P,t[rc].sum%=P;
t[lc].add+=t[p].add,t[lc].add%=P;
t[rc].add+=t[p].add,t[rc].add%=P;
t[p].add=0;
}
}
void bd(int p,int l,int r){
t[p]={l,r,0,0,0,1};
if(l==r){
t[p].sum=a[l];
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){
if(qop==1){ //区间加
t[p].sum2+=t[p].sum*(t[p].r-t[p].l)%P*qv%P+(t[p].r-t[p].l+1)*(t[p].r-t[p].l)/2%P*qv%P*qv%P,t[p].sum2%=P;
t[p].sum+=(t[p].r-t[p].l+1)*qv%P,t[p].sum%=P;
t[p].add+=qv,t[p].add%=P;
}else{ //区间乘
t[p].sum2*=qv*qv%P,t[p].sum2%=P;
t[p].sum*=qv,t[p].sum%=P;
t[p].mul*=qv,t[p].mul%=P;
t[p].add*=qv,t[p].add%=P;
}
return ;
}
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(ql<=mid) update(lc);
if(qr>mid) update(rc);
pushup(p);
}
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));
}
void updt(int l,int r,int op,ll v){
ql=l,qr=r;
qop=op,qv=v;
update(1);
}
ll ask_sum(int l,int r){
ql=l,qr=r;
return query(1).sum;
}
ll ask_sum2(int l,int r){
ql=l,qr=r;
return query(1).sum2;
}
}tr;
void solve(){
int m; cin >> n >> m >> P;
for(int i=1;i<=n;i++) cin >> tr.a[i];
tr.bd(1,1,n);
while(m--){
int op; cin >> op;
if(op==3){
int l,r; cin >> l >> r;
cout << tr.ask_sum2(l,r)%P << "\n";
}else{
int l,r,v; cin >> l >> r >> v;
tr.updt(l,r,op,v);
}
}
}
下午暑假友谊赛热身1
贴个D atc传送门
题意:将 a数组切三刀分成四段,使四段中的最大sum值和最小sum值差值最小
思路:注意到切三刀这个操作,先考虑能不能枚举中间那刀,答案是可以的,两边的最佳切法为将值尽量对半开,二分lower_bound找到切点,因为lower_bound是大于等于值的一半,所以注意检查切点往左移一位是否更优
代码如下:
cpp
const int N=3e5+10;
ll n;
ll a[N];
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]+=a[i-1];
}
ll ans=1e18;
for(int mid=2;mid<=n-2;mid++){
ll l=a[mid],r=a[n]-a[mid];
int i=lower_bound(a+1,a+n+1,l/2)-a;
if(i>1 && abs(a[mid]-a[i]*2)>abs(a[mid]-a[i-1]*2)) i--;
int j=lower_bound(a+1,a+n+1,a[mid]+r/2)-a;
if(j>mid+1 && abs(a[n]+a[mid]-a[j]*2)>abs(a[n]+a[mid]-a[j-1]*2)) j--;
ll ma=max({a[i],a[mid]-a[i],a[j]-a[mid],a[n]-a[j]});
ll mi=min({a[i],a[mid]-a[i],a[j]-a[mid],a[n]-a[j]});
ans=min(ma-mi,ans);
}
cout << ans;
}
星期五:
补cf round731 div3 cf传送门
思路:注意到一部空调可以对两端都造成影响,遇到这种类型的题,优先考虑把左端和右端分开考虑。l【i】表示只考虑左边空调影响的最低温度,r【i】表示只考虑右边空调影响的最低温度
l【i】= min(l【i-1】+ 1,l【i】),r【i】= min(r【i+1】+1,r【i】),答案取更小值
代码如下:
cpp
const int N=3e5+10,M=210;
ll n;
int a[N],t[N];
int l[N],r[N];
void solve(){
int k; cin >> n >> k;
for(int i=0;i<=n+1;i++) l[i]=r[i]=2e9;
for(int i=1;i<=k;i++) cin >> a[i];
for(int i=1;i<=k;i++){
cin >> t[i];
l[a[i]]=r[a[i]]=t[i];
}
for(int i=1;i<=n;i++)
l[i]=min(l[i-1]+1,l[i]);
for(int i=n;i;i--)
r[i]=min(r[i+1]+1,r[i]);
for(int i=1;i<=n;i++) cout << min(l[i],r[i]) << " \n"[i==n];
}
补cf round 957 div3 D cf传送门
很恶心一道模拟,但放上来的重点不是这个,而是提醒自己特判要等到输入完成之后!!!
cpp
int m,k; cin >> n >> m >> k;
if(m>n){cout << "YES\n"; return ;}
string s; cin >> s; s=" "+s;
此为错误代码,也是我赛时写的,将m>n的特判放到 cin >> s后,就能过了
!!!!!!!!!!!!!!!!!!警钟长鸣!!!!!!!!!!!!!!!!!!!!!
板刷数据结构 H 牛客传送门
思路:维护全局最大值,然后线段树上二分
需要注意两点,第一点是merge时,res不能嗯取 a,b的最大值,取 ma1的前提是子节点ma2>0,第二点是输出 -1后是 contnue而不是 return
代码如下:
cpp
const int N=3e5+10;
ll n;
int m,k;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
ll ma1,ma2;
}t[N<<2];
int ql,qr,qv;
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
if(a.ma2 && b.ma2) res.ma1=max(a.ma1,b.ma1),res.ma2=1;
else if(a.ma2) res.ma1=a.ma1,res.ma2=1;
else if(b.ma2) res.ma1=b.ma1,res.ma2=1;
else res.ma1=res.ma2=0;
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,m,k};
if(l==r) return ;
int mid=l+r>>1;
bd(lc,l,mid);
bd(rc,mid+1,r);
}
void update(int p){
if(ql<=t[p].l && qr>=t[p].r){
t[p].ma1-=qv;
t[p].ma2--;
return ;
}
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);
}
int fnd(int p,int a){
if(t[p].l==t[p].r) return t[p].l;
if(t[lc].ma1>=a && t[lc].ma2>0) return fnd(lc,a);
return fnd(rc,a);
}
}tr;
void solve(){
cin >> n >> m >> k;
tr.bd(1,1,n);
for(int i=1;i<=n;i++){
int a; cin >> a;
if(tr.t[1].ma1<a || tr.t[1].ma2<=0){cout << "-1\n"; continue;}
int res=tr.fnd(1,a);
tr.updt(res,res,a);
cout << res << "\n";
}
}
星期六:
上午友谊赛
贴个ABC123 D atc传送门
和 hdoj的大雪球很像
思路:狠狠地二分求出第 k个的值,然后把大于此值的存入,排序输出,操作有点复杂,写了挺久
代码如下:
cpp
ll n;
ll a[1010],b[1010],c[1010];
void solve(){
ll x,y,z,pq; cin >> x >> y >> z >> pq;
for(int i=1;i<=x;i++) cin >> a[i];
for(int i=1;i<=y;i++) cin >> b[i];
for(int i=1;i<=z;i++) cin >> c[i];
sort(a+1,a+x+1);
sort(b+1,b+y+1);
sort(c+1,c+z+1);
ll l=a[1]+b[1]+c[1],r=a[x]+b[y]+c[z],res=0;
while(l<=r){ //二分出第 k个的体积
ll mid=l+r>>1;
ll sum=0;
for(int i=1;i<=x;i++){
for(int j=1;j<=y;j++){
int k=lower_bound(c+1,c+z+1,mid-a[i]-b[j])-c;
sum+=z-k+1; //算出大于此体积的个数
}
}
if(sum>=pq) res=mid,l=mid+1;
else r=mid-1;
}
vector<ll>ans; ll cnt=0;
for(int i=x;i;i--){
if(a[i]+b[y]+c[z]<res) break;
for(int j=y;j;j--){
if(a[i]+b[j]+c[z]<res) break;
for(int k=z;k;k--){
if(a[i]+b[j]+c[k]<res) break;
ans.push_back(a[i]+b[j]+c[k]);
cnt++;
if(cnt>pq*1000) break; //塞少了会wa
}
if(cnt>pq*1000) break;
}
if(cnt>pq*1000) break;
}
sort(ans.begin(),ans.end(),greater<>());
for(int i=0;i<pq;i++) cout << ans[i] << "\n";
}
贴个ABC204 D atc传送门
思路:典题,正解类似于背包,一维枚举物品,二维枚举体积,这里是dfs加个记忆化
代码如下:
cpp
const int N=2e5+10,M=210;
ll n;
int t[1010];
ll ans;
ll dp[110][N];
ll dfs(int x,int sum1,int sum2){
if(x>n) return max(sum1,sum2);
if(max(sum1,sum2)>=ans) return dp[x][sum1]=INT_MAX;
if(dp[x][sum1]!=-1) return dp[x][sum1];
if(dp[x][sum2]!=-1) return dp[x][sum2];
ll res=0;
res=min(dfs(x+1,sum1+t[x],sum2),dfs(x+1,sum1,sum2+t[x]));
return dp[x][sum1]=res;
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> t[i];
sort(t+1,t+n+1,greater<>());
int sum1=0,sum2=0;
for(int i=1;i<=n;i++){
if(sum1<sum2) sum1+=t[i];
else sum2+=t[i];
}
ans=max(sum1,sum2);
memset(dp,-1,sizeof dp);
ans=min(dfs(1,0,0),ans);
cout << ans;
}
补cf round734 D1 cf传送门
题意:在n*m的格子里放k个1*2的骨牌,剩下的放 2*1的,能否放满
思路:分类讨论,n和m的奇偶性有三种情况
n和m都为偶,k奇数NO,偶数YES
n为奇,考虑将其转化为第一种情况,先用 m/2个横牌填满一行,若填不了也肯定NO
m为奇,也考虑如何转化为第一种情况,用 n/2个竖牌填一列 (判断 k的大小),再判断 k奇偶
代码如下:
cpp
ll n;
void solve(){
ll m,k; cin >> n >> m >> k;
if(n!=2 && k==n*m/2-1){cout << "NO\n"; return ;}
if(!(n&1) && !(m&1)){
if(k&1) cout << "NO\n";
else cout << "YES\n";
}else if(!(n&1)){
if(k>n*m/2-n/2) cout << "NO\n";
else if(k&1) cout << "NO\n";
else cout << "YES\n";
}else{
if(k>=m/2){
k-=m/2;
if(k&1) cout << "NO\n";
else cout << "YES\n";
}else cout << "NO\n";
}
}
补ABC 067 D atc传送门
思路:容易想到两人应该先抢占 1-n的简单路径,抢完后谁能涂的点多谁就赢
dis【i】表示 i到俩人的距离,disb【i】<= disw【i】,此点就是F的,否则S的
代码如下:
cpp
const int N=2e5+10,M=210;
ll n;
vector<int>ve[N];
int disb[N],disw[N];
int sumb,sumw;
void dfsb(int x,int f){
for(int v:ve[x]){
if(v==f) continue;
disb[v]=disb[x]+1;
dfsb(v,x);
}
}
void dfsw(int x,int f){
for(int v:ve[x]){
if(v==f) continue;
disw[v]=disw[x]+1;
dfsw(v,x);
}
}
void painb(int x,int f){
if(disb[x]>disw[x]) return ;
sumb++;
for(int v:ve[x]){
if(v==f) continue;
painb(v,x);
}
}
void painw(int x,int f){
if(disw[x]>=disb[x]) return ;
sumw++;
for(int v:ve[x]){
if(v==f) continue;
painw(v,x);
}
}
void solve(){
cin >> n;
for(int i=1;i<n;i++){
int a,b; cin >> a >> b;
ve[a].push_back(b);
ve[b].push_back(a);
}
dfsb(1,0);
dfsw(n,0);
painb(1,0);
painw(n,0);
if(sumb<=sumw) cout << "Snuke";
else cout << "Fennec";
}
补cf round760 div3 F cf传送门
思路:第一眼可能会让人摸不着头脑,但手操下操作会发现,x能变为的数是十分有限的
若加0翻转,实际等于去除末尾的0再翻转,连续的有效操作次数最多为2
若加1翻转,等于翻转后加一个高位的1,这样操作在60次后就一定会超过1e18
所以可直接暴力模拟
代码如下:
cpp
string to_s(ll x){
string s;
while(x){
if(x&1) s.append("1");
else s.append("0");
x>>=1;
}
reverse(s.begin(),s.end());
return s;
}
void solve(){
ll x,y; cin >> x >> y;
string sx=to_s(x),sy=to_s(y);
set<string>st;
st.insert(sx);
queue<string>qu;
qu.push(sx);
while(!qu.empty()){
string t=qu.front(); qu.pop();
if(t.size()>60) continue;
string xs=t;
while(xs.back()=='0') xs.pop_back();
reverse(xs.begin(),xs.end());
if(st.find(xs)==st.end()){
st.insert(xs);
qu.push(xs);
}
t.append("1");
reverse(t.begin(),t.end());
if(st.find(t)==st.end()){
st.insert(t);
qu.push(t);
}
}
if(st.find(sy)!=st.end()) cout << "YES";
else cout << "NO";
}
周日:
补 ABC207 E atc传送门
思路:dp【i】【j】表示考虑到第 i个元素,分成 j段的方案数,朴素转移复杂度为 O(n^3)
dp【i】【j】从 dp【k】【j-1】转移的条件是 ( a【i】- a【k】)% j == 0,也就是 a【i】与 a【k】同模 j,那么可以用 sum提前存下分成 j-1段及其对 j模数为 a【i】%j的总方案数(有点抽象
代码如下:
cpp
const int mod=1e9+7;
ll n;
ll a[3030];
ll dp[3030][3030];
ll sum[3030][3030];
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]+=a[i-1];
}
for(int i=1;i<=n;i++){
dp[i][1]=1;
for(int j=2;j<=i;j++)
dp[i][j]=sum[j][a[i]%j];
for(int j=1;j<=n;j++)
(sum[j][a[i]%j]+=dp[i][j-1])%=mod;
}
ll ans=0;
for(int i=1;i<=n;i++) (ans+=dp[n][i])%=mod;
cout << ans;
}