DAY 1
优美的字符串
题目描述
给出一个只有'a'和'b'的字符串,希望通过最少的操作后使得所有'b'都在'a'前面。一种操作:每次选取一个'ab'子串变成'bba'。
答案对1e9+7取模,如果不能得到输出-1.
n<=1e6
题解
可以发现当两个'a'在一起的时候前面的'a'不可能变到后面去(废话),所以可以得出答案一定。
考虑先移动后面的'a',可以发现一个'a'移动的次数就是后面'b'的个数,并且这次移动后'b'的个数翻倍。
所以就记录现在'b'的个数和答案就OK了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=1000005; const int mod=1000000007; int n; char s[maxn]; ll ans,cnt;//cnt:现在b的个数(包括变换产生的 int main(){ freopen("string.in","r",stdin); freopen("string.out","w",stdout); scanf("%s",s+1); n=strlen(s+1); for(int i=n;i;i--){ if(s[i]=='a'){ ans=(ans+cnt)%mod;//移动cnt就好 cnt=2*cnt%mod;//每移动一次都会再带来一个b } else cnt=(cnt+1)%mod; } printf("%lld",ans%mod); return 0; }
数字谜题
为了让这一部分有意义,再介绍一下组合数的方法:
首先预处理还是一样的,我们需要找出有哪些在[1,1000]内的数可以通过k-1步到达1,然后加入一个vector。
然后对于加入的每个数进行组合数版的dfs,因为我们知道一共需要多少个1.
城市规划
题目描述
给出一个n个点n条边的图,保证联通。问删去一条边之后最长的路径最短是多少。
对于100% 数据,3<=n<=200000; 1<=w<=10e9
题解
CF835F
考虑暴力就是枚举环上的边,删去之后找直径,求最小的直径。(加上个卡时就看人品了)
考虑这就是一个基环树,想象将环摆在中间,环上的每个节点有一个树。
考虑对于删去一条边后什么会成为直径:每颗树的直径或者两个两棵树中到根最长链+两个根的距离。
第一个是无论我们删哪个边都不变的。
所以就考虑第二个怎么维护。
先把环按顺序存下来,假设有m个点,维护每个点到环上第一个点的距离qzh,环上每个点的树中到根最长长度l,如果删去$s_{i},s_{i+1}$的连边:
设x<y
在选取1..i的两个点,(lx-qzhx)+(ly+qzhy)
在i+1...m,和上面一样
在两部分分别选一个点,tot为环的边权和,tot+(lx+qzhx)+(ly-qzhy)
为了快速的求解,记录pre为第一种情况的前缀最大值,suf为第二种情况的后缀最大值,对于第三种就记录lx+qzhx,lt-qzhy的前缀最大值即可。
#include<ctime> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=200005; const ll inf=10000000000000000; int n,cnt,rt,head[maxn]; int top,s[maxn],dis[maxn];//存环和点到下个点的距离 ll ret,f[maxn];//ret:树中的最大直径 bool vis[maxn],circle[maxn]; struct edge{ int x,y,next; ll val; }e[maxn<<1]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x= f ? -x : x ; } void print(ll x){ if(x<0) putchar('-'),x=-x; if(x>9) print(x/10); putchar(x%10+'0'); } ll max(ll x,ll y){return x>y ? x : y ;} ll min(ll x,ll y){return x<y ? x : y ;} inline void add(int x,int y,int val){ e[++cnt]=(edge){x,y,head[x],val}; head[x]=cnt; } int dfs(int x,int fa){//找环 if(vis[x]) {rt=x;return 1;} vis[x]=true; for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa) continue; int tmp=dfs(y,x); if(tmp){ if(tmp==1){ s[++top]=x; dis[top]=e[i].val; circle[x]=true; if(x!=rt) return 1; } return 2; } } return 0; } ll tot,l[maxn],qzh[maxn];//每颗树中到根最长的链,环上权值和 ll pre[maxn],suf[maxn]; ll cpre[maxn],csuf[maxn]; void dfs1(int x,int fa,int root){ for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa||circle[y]) continue; f[y]=f[x]+e[i].val; if(l[root]<f[y]) l[root]=f[y],rt=y; dfs1(y,x,root); } } void dfs2(int x,int fa,int root){ for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa) continue; if(circle[y]&&y!=root) continue; f[y]=f[x]+e[i].val; if(ret<f[y]) ret=f[y]; dfs2(y,x,root); } } void find_d(int x){//树的直径 f[x]=0; dfs1(x,0,x); f[rt]=0; dfs2(rt,0,x); } void solve(){ dfs(1,0); for(int i=1;i<=top;i++) find_d(s[i]); for(int i=2;i<=top;i++){ qzh[s[i]]=qzh[s[i-1]]+dis[i]; tot+=dis[i]; } tot+=dis[1]; ll cur=l[s[1]]-qzh[s[1]]; for(int i=2;i<=top;i++){ pre[s[i]]=max(pre[s[i-1]],cur+l[s[i]]+qzh[s[i]]); cur=max(cur,l[s[i]]-qzh[s[i]]); } cur=l[s[top]]+qzh[s[top]]; for(int i=top-1;i;i--){ suf[s[i]]=max(suf[s[i+1]],cur+l[s[i]]-qzh[s[i]]); cur=max(cur,l[s[i]]+qzh[s[i]]); } cpre[s[1]]=l[s[1]]+qzh[s[1]]; for(int i=2;i<=top;i++) cpre[s[i]]=max(cpre[s[i-1]],l[s[i]]+qzh[s[i]]); csuf[s[top]]=l[s[top]]-qzh[s[top]]; for(int i=top-1;i;i--) csuf[s[i]]=max(csuf[s[i+1]],l[s[i]]-qzh[s[i]]); ll ans=inf; for(int i=1;i<top;i++){ ll tmp=ret; tmp=max(tmp,pre[s[i]]); tmp=max(tmp,suf[s[i+1]]); tmp=max(tmp,cpre[s[i]]+csuf[s[i+1]]+tot); ans=min(ans,tmp); } printf("%lld",ans); } int main(){ bool opt=true; read(n); for(int i=1;i<=n;i++){ int x,y; ll val; read(x);read(y);read(val); add(x,y,val); add(y,x,val); } solve(); return 0; }
但是CF没过,T了没看懂。
DAY 2
修路
有一个n个点m条边的无向图,边权为1,现加入一条边,问有多少种加边的方案不能使得s到t的距离变短。
加边(i,j)和(j,i)算一种,加边原图不能有。
对于100% 数据,2<=n<=1000; n-1<=m<=2000;1<=S,T<=n; S!=T。保证图联通。
题解
一开始想的很简单,因为n很小,求出两两之间的最短距离,用bfs求一次是O(n)的吧。
然后枚举边(i,j),判断是否有这条边就看dis[i][j]是否为1,为了检验这条路的影响,所以一定到走这条路,所以看s-i-j-y和s-j-i-y是否都不行即可。
但是发现每次固定的是s,t,而且枚举的边权为1,所以只要知道s到每个点和t到每个点的距离拼接即可,不过在判断是否原图有的时候需要多开一个数组记录。
#include<ctime> #include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1006; const int maxm=2006; int n,m,dx,dy; int cnt,head[maxn]; int dis[2][maxn]; bool mp[maxn][maxn]; struct edge{ int x,y,next; }e[maxm<<1]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } void add(int x,int y){ e[++cnt]=(edge){x,y,head[x]}; head[x]=cnt; } void bfs(int k,int st){ queue<int> q; dis[k][st]=0; q.push(st); while(!q.empty()){ int x=q.front(); q.pop(); for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(dis[k][y]==-1){ dis[k][y]=dis[k][x]+1; q.push(y); } } } } int main(){ freopen("road.in","r",stdin); freopen("road.out","w",stdout); read(n);read(m);read(dx);read(dy); for(int i=1;i<=m;i++){ int x,y; read(x);read(y); add(x,y); add(y,x); mp[x][y]=mp[y][x]=true; } memset(dis,-1,sizeof(dis)); bfs(0,dx);bfs(1,dy); int now=dis[0][dy]; cnt=0; for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) if(!mp[i][j]) if(dis[0][i]+1+dis[1][j]>=now&&dis[0][j]+1+dis[1][i]>=now) cnt++; printf("%d",cnt); }
神奇的集合
题目描述
有n个神奇的集合,编号为1-n,开始都为空,先进行两种操作:
将x加入编号[l,r]的集合,如果一个集合本来就有x,那么该集合所有元素个数加倍(并且就不用将这个x加进去)
查询[l,r]的集合的元素和,对998244353取模。
对于100% 数据,1<=n; q<= 200000,1<=opt<=2,1<=l<=r<=n,1<=x<=n。
题解
暴力就是各种分治。不过可以发现一点就是如果这个集合本来有一个元素,那么这次就+1,不然就*2.
所以开n颗线段树维护对于元素i每个位置的情况,再开一个答案线段树。
考虑区间修改,如果当前区间都没有x,就区间+1,并且记录区间有了x;如果都有x,就区间*2;不然就继续递归。
一个区间最多被赋值一次,所以时间复杂度O(nlogn)(???)
#include<map> #include<ctime> #include<queue> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=200005; const int mod=998244353; int n,m,cnt; int root[maxn],ls[maxn<<5],rs[maxn<<5]; ll tag[maxn<<5],mul[maxn<<5],sum[maxn<<5]; int num[maxn<<5]; bool lazy[maxn<<5]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } void print(ll x){ if(x<0) x=-x,putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); } void update1(int rt){ sum[rt]=(sum[ls[rt]]+sum[rs[rt]])%mod; } void update2(int rt){ num[rt]=num[ls[rt]]+num[rs[rt]]; } void put_lazy(int &rt,int l,int r){ if(!rt) rt=++cnt; lazy[rt]=true; num[rt]=r-l+1; } void push_down2(int rt,int l,int r){ int mid=(l+r)>>1; put_lazy(ls[rt],l,mid); put_lazy(rs[rt],mid+1,r); lazy[rt]=false; } void put_mul(int &rt,ll val){ if(!rt) {rt=++cnt;mul[rt]=1;} sum[rt]=sum[rt]*val%mod; mul[rt]=mul[rt]*val%mod; tag[rt]=tag[rt]*val%mod; } void put_tag(int &rt,int l,int r,ll val){ if(!rt) {rt=++cnt;mul[rt]=1;} sum[rt]=(sum[rt]+val*(r-l+1))%mod; tag[rt]=(tag[rt]+val)%mod; } void push_down1(int rt,int l,int r){ int mid=(l+r)>>1; if(mul[rt]!=1){ put_mul(ls[rt],mul[rt]); put_mul(rs[rt],mul[rt]); mul[rt]=1; } if(tag[rt]){ put_tag(ls[rt],l,mid,tag[rt]); put_tag(rs[rt],mid+1,r,tag[rt]); tag[rt]=0; } } void modify(int &rt1,int &rt2,int l,int r,int a_l,int a_r){ if(!rt1) {rt1=++cnt;mul[rt1]=1;} if(!rt2) rt2=++cnt; if(a_l<=l&&r<=a_r){ if(!num[rt2]) {put_lazy(rt2,l,r),put_tag(rt1,l,r,1);return ;} else if(num[rt2]==r-l+1) {put_mul(rt1,2);return ;} } int mid=(l+r)>>1; if(lazy[rt2]) push_down2(rt2,l,r); push_down1(rt1,l,r); if(a_l<=mid) modify(ls[rt1],ls[rt2],l,mid,a_l,a_r); if(mid<a_r) modify(rs[rt1],rs[rt2],mid+1,r,a_l,a_r); update1(rt1); update2(rt2); } ll query(int rt,int l,int r,int a_l,int a_r){ if(a_l<=l&&r<=a_r) return sum[rt]; push_down1(rt,l,r); ll ret=0; int mid=(l+r)>>1; if(a_l<=mid) ret+=query(ls[rt],l,mid,a_l,a_r); if(mid<a_r) ret+=query(rs[rt],mid+1,r,a_l,a_r); return ret%mod; } int main(){ freopen("multiset.in","r",stdin); freopen("multiset.out","w",stdout); read(n);read(m); for(int i=1;i<=m;i++){ int opt,l,r; read(opt);read(l);read(r); if(opt==1){ int x;read(x); modify(root[0],root[x],1,n,l,r); } else printf("%lld ",query(root[0],1,n,l,r)); } }
还有空间开大点。
缩树游戏
给出一个n个点的树,每次等概率选一条边(x,y)删去,并且等概率留下其中一个节点,这个节点和所有与x和y连边的点连边。对于每个点求最后剩下的是它的概率。
2<=n<=50
题解
......