初二上 合集

开学计划 & TOCO_R1划水记 & 寒假杂记

这是迄今为止打得最爽的一场比赛,非常适合我这种大模拟懒得写,CF不会写(最近都要刷CF)的大菜鸡。但高分只是偶然,千万不能骄傲。重点是:

中奖了,拿了5RMB!!!

我是先开B再开A的,因为开场几分钟就有人A了B。我先打了个n!暴力,再结合很显然的结论,用dp做到了n^2。拿了40分之后就发现正解的k不超过20,但是20*1e6刚好longlong会MLE(出题人就是这么想的),就换成int,计算的时候强转longlong 。本以为这样会WA,试了一下竟然A了,神奇!

A一眼题,非常经典,离散化了之后直接按右端点排序贪心选区间,写棵线段树就行了。

C、D瞎写一通,不会。5ab神仙竟然写出来了,差点一血,吊打std,被良心出题人关注。

(其实我就奇怪为啥这场明明不是很难,300+的人只有4个,估计是开学了吧。如果暴力打满刚好300,不过这样我就中不了奖了,rk4会少拿2RMB。。。。)

这可能是寒假最后一场比赛了,总的来说,这个寒假除了作业写得有点慢,其他方面都是收获颇丰的。(毕竟WC Cu了呢!)


WC2021划水记

其实本来不打算写,但因为这是人生中获得的第一个稍微有点含金量的奖项,虽然只是Cu,但是不写一下心里很不舒服。投稿是肯定不会投稿了。

Day-7期末考试
自认为除了数学,其他都考的不错

Day-5收到成绩,心如死水。反正下个学期黑马是稳了。

Day-4~0 写寒假作业,电脑碰都没碰。

Day1
开题
完全不会做,反正是WC怎么可能AC,随便打了三个暴力

Day2草草草竟然Cu了?

彡彡彡彡彡彡彡彡彡彡彡彡彡彡彡彡


MCOI-03 括号

这道题建模非常巧妙。

突破口:做过CSP19 D1T2的应该知道,一个子串的0级偏值就是这个子串中没有匹配的括号数(废话)

因为一个括号在整个串中最多只有一个括号能与之匹配,所以记录与右括号 \(i\) 匹配的左括号位置为 \(a_i\)

先求1级偏值:考虑逆向思考,即计算每对 \([a_i,i]\) 中有多少对区间只包含 \(a_i,i\) 中的一个,这个可以用数学方法求出来: \(Ans=(i-a_i) \times(n-(i-a_i)+1)\)

接下来是这道题最巧妙的地方,如果一个位置能对第k层区间产生贡献,相当于向左跳\(k+1\)步,向右跳\(k+1\)步不能超出边界。这个方案数即为\(C(n+k,k)\)

还存在一个问题,向左/向右的第一步不能超出\([a_i,i)\)\((a_i,i]\)。这可以用前缀和把组合数预处理出来。

又臭又长的代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2000005;
int n,k;
char s[N];
int q[N],tot,a[N],vis[N];
typedef long long ll;
const ll mod=998244353;
ll f[N],f2[N],fac[N],sum[N];
ll pw(ll x,ll y){
	ll res=1;
	while(y){
		if(y&1)res=(res*x)%mod;
		x=(x*x)%mod;
		y>>=1; 
	}
	return res;
}
int main(){
	cin>>n>>k;
	int i,j;
	scanf("%s",s+1);
	for(i=1;i<=n;i++){
		if(s[i]=='('){
			q[++tot]=i;
		}else {
			if(tot>0)vis[i]=vis[q[tot]]=1,a[i]=q[tot--];
			else a[i]=i;
		}
	}
	ll ans=0;
	fac[0]=1;
	for(i=1;i<=n+k;i++){
		fac[i]=(fac[i-1]*i)%mod;
	}
	f[0]=1,f2[0]=1;
	for(i=1;i<=n;i++){
		f[i]=fac[i+k-1]*pw(fac[i]*fac[k-1]%mod,mod-2)%mod;
		f2[i]=fac[i+k]*pw(fac[i]*fac[k]%mod,mod-2)%mod;
	}
	sum[0]=1;
	for(i=1;i<=n;i++)sum[i]=(sum[i-1]+f[i])%mod;
	for(i=1;i<=n;i++){
		if(vis[i]==0){
			ans=(ans+f2[i-1]*f2[n-i]%mod)%mod;
		}else{
			if(s[i]==')'){
				ans=(ans+(sum[n-a[i]]-sum[n-i]+mod)%mod*f2[a[i]-1]%mod)%mod;
				ans=(ans+(sum[i-1]-sum[a[i]-1]+mod)%mod*f2[n-i]%mod)%mod;
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

TJOI2019 大中锋的游乐场

分层图最短路裸题

#include<bits/stdc++.h>
using namespace std;
const int N=2000005;
int t,n,m,k;
int a[N],h[N],cnt,nxt[N],val[N],to[N];
int d(int x,int y){
	return y*n+x;
}
void add(int x,int y,int z){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y,val[cnt]=z;
}
typedef long long ll;
int vis[N];
ll dis[N];
queue<int> q;
void spfa(int s){
	int i;
	for(i=1;i<=n*2*(k+2);i++)dis[i]=1e15,vis[i]=0;
	vis[s]=1,dis[s]=0,q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(i=h[x];i;i=nxt[i]){
			int v=to[i],w=val[i];
			if(dis[v]>dis[x]+w){
				dis[v]=dis[x]+w;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
}
int main(){
	int i,j;
	cin>>t;
	while(t--){
		cnt=0;
		memset(h,0,sizeof(h));
		memset(nxt,0,sizeof(nxt));
		memset(to,0,sizeof(to));
		memset(val,0,sizeof(val));
		memset(dis,0,sizeof(dis));
		memset(vis,0,sizeof(vis));
		scanf("%d%d%d",&n,&m,&k);
		for(i=1;i<=n;i++)scanf("%d",&a[i]);
		for(i=1;i<=m;i++){
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			if(a[y]==1){
				for(j=0;j<2*k;j++){
					add(d(x,j),d(y,j+1),z);
				}
			}
			if(a[x]==1){
				for(j=0;j<2*k;j++){
					add(d(y,j),d(x,j+1),z);
				}
			}
			if(a[y]==2){
				for(j=1;j<=2*(k+1);j++){
					add(d(x,j),d(y,j-1),z);
				}
			}
			if(a[x]==2){
				for(j=1;j<=2*(k+1);j++){
					add(d(y,j),d(x,j-1),z);
				}
			}
		}
	}
	int s,t;
	cin>>s>>t;
	if(a[s]==1){
		spfa(d(s,k+1));
		ll ans=1e15;
		for(i=0;i<=2*k;i++)ans=min(ans,dis[d(t,i)]);
		if(ans==1e15) cout<<-1<<endl;
		else cout<<ans<<endl;
	}
	if(a[s]==2){
		spfa(d(s,k-1));
		ll ans=1e15;
		for(i=0;i<=2*k;i++)ans=min(ans,dis[d(t,i)]);
		if(ans==1e15) cout<<-1<<endl;
		else cout<<ans<<endl;
	}
	return 0;
} 

JSOI2016 反质数序列

挑出一个子集,“两两”满足某某条件的题可以考虑下二分图最大独立集

最大独立集的定义:最大的一个子集,两两之间没有边相连。

最大独立集=总点数-最大匹配

如果我们将和为质数的两个点连边,我们可以发现一个神奇的性质:a+b为质数
,b+c为质数,则a+c不可能为质数。(1不能和别的1连,所以只保留一个1)

然后考虑如何将这个图分成两类点使之形成二分图,很显然,按奇偶性分。

然后,愉快地跑一遍二分图最大独立集就结束了,还要卡卡常

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
const int maxn=3010;
using namespace std;
int zs[100005],num=0;
bool v[200005];
int n,a[maxn],S,T,t=-1;
vector<int> g[maxn];
struct lines{
    int to,flow,cap;
}l[maxn*maxn];
int d[maxn],cur[maxn];
bool vis[maxn];
 
inline void add(int from,int to,int cap){
    l[++t]=(lines){to,0,cap},g[from].pb(t);
    l[++t]=(lines){from,0,0},g[to].pb(t);
}
 
inline bool BFS(){
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(S),d[S]=0,vis[S]=1;
   
    int x; lines e;
    while(!q.empty()){
        x=q.front(),q.pop();
        for(int i=g[x].size()-1;i>=0;i--){
            e=l[g[x][i]];
            if(e.flow<e.cap&&!vis[e.to]){
                vis[e.to]=1,d[e.to]=d[x]+1;
                q.push(e.to);
            }
        }
    }
   
    return vis[T];
}
 
int dfs(int x,int A){
    if(x==T||!A) return A;
    int flow=0,f,sz=g[x].size();
    for(int &i=cur[x];i<sz;i++){
        lines &e=l[g[x][i]];
        if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(e.cap-e.flow,A)))){
            A-=f,e.flow+=f;
            flow+=f,l[g[x][i]^1].flow-=f;
            if(!A) break;
        }
    }
    return flow;
}
 
inline int max_flow(){
    int an=0;
    while(BFS()){
        memset(cur,0,sizeof(cur));
        an+=dfs(S,1<<30);
    }
    return an;
}
 
inline void init(){
    for(int i=2;i<=200000;i++){
        if(!(v[i])) zs[++num]=i;
        for(int j=1,u;j<=num&&(u=zs[j]*i)<=200000;j++){
            v[u]=1;
            if(!(i%zs[j])) break;
        }
    }
}
 
inline void solve(){
    for(int i=1;i<=n;i++)
        if(a[i]&1) add(S,i,1);
        else add(i,T,1);
    for(int i=1;i<=n;i++) if(a[i]&1)
        for(int j=1;j<=n;j++) if(!(a[j]&1)&&!v[a[i]+a[j]]) add(i,j,1);
    printf("%d\n",n-max_flow());
}
 
int main(){
    init();
    scanf("%d",&n),S=0,T=n+1;
    for(int i=1;i<=n;i++){
        scanf("%d",a+i);
        if(a[i]==1){
            if(v[1]) i--,n--;
            else v[1]=1;
        }
    }
   
    solve();
   
    return 0;
}

EZEC-4 可乐

一开始没有想出来,觉得一定有一个固定结论,后来发现只要逆向思考一下就好了。

对于从一堆ai中找出一个对所有ai最优的x,这种问题可以枚举每个ai,求出所有对应x(1...n)的价值,最后把所有ai的价值加一下,取个最大值就好了。

对于这道题,容易发现,对于每个ai,可能取到ai的x值只有Logn段,且在二进制下a<=x就相当于在x的每个位(i)中,ai必定有一位<xi,且在这一位以后可以随便取,都<x了,可以随便取。

问题就被转化成了枚举一个第i位,算出ai这一位<k,x的可能取值区间,这只有log个区间,差分一下加到一起去就行了

#include<bits/stdc++.h>
using namespace std;
const int N=5000005;
int n,k,p[30],c[30],a[N],s[N];
void f(int x){
	int i;
	memset(p,0,sizeof(p));
	memset(c,0,sizeof(c));
	int cnt=0,tot=0,w=k;
	while(w){
		p[++cnt]=w%2;
		w/=2;
	}
	for(i=1;i<=cnt/2;i++)swap(p[i],p[cnt-i+1]);
	w=x;
	while(w){
		c[++tot]=w%2;
		w/=2;
	}
	for(i=1;i<=tot/2;i++)swap(c[i],c[tot-i+1]);
	if(cnt<tot){
		for(i=tot;i>tot-cnt;i--)p[i]=p[i-(tot-cnt)];
		for(i=tot-cnt;i>=1;i--)p[i]=0;
		cnt=tot;
	}
	if(cnt>tot){
		for(i=cnt;i>cnt-tot;i--)c[i]=c[i-(cnt-tot)];
		for(i=cnt-tot;i>=1;i--)c[i]=0;
		tot=cnt;
	}
	int res=0,ans=0;
	for(i=1;i<=cnt;i++){
		res=p[i]^c[i];
		ans<<=1;
		if(p[i]==1){
			s[(ans+c[i])<<(cnt-i)]++;
			s[(ans+c[i]+1)<<(cnt-i)]--;
		}
		ans+=res;
	}
	s[ans]++,s[ans+1]--; 
}
int main(){
	int i;
	cin>>n>>k;
	for(i=1;i<=n;i++){
		scanf("%d",&a[i]);
		f(a[i]);	
	} 
	int ans=s[0];
	for(i=1;i<=(1<<21);i++)s[i]+=s[i-1],ans=max(ans,s[i]);
	cout<<ans<<endl; 
	return 0;
}

P5623 基础最优化练习题

这道题还是不错的,至少去年CSP的时候我一直想不出来。。。

一个很巧妙的转换:bi改变了,那wi的后缀和(记为si)也会改变。所以对于si>0的点,ans+=ksi,反之对于si<0的点,ans-=ksi

然后就要通过减小一些k使k的前缀和<=ai,且(减小的值)*(si)尽可能小,那就可以维护一个关于si的小根堆,同时保存k(k至多减去2k),贪心就行了

#include<bits/stdc++.h>
using namespace std;
int n,k;
const int N=1000005;
typedef long long ll;
ll a[N],w[N],s[N];
struct node{
	ll s,v; 
	bool operator <(const node& x) const{
		return s>x.s; 
	}
};
priority_queue<node> q;
int main(){
	int i;
	cin>>n>>k;
	for(i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(i=1;i<=n;i++)scanf("%lld",&w[i]);
	for(i=n;i>=1;i--)s[i]=s[i+1]+w[i];
	ll now=0,ans=0;
	for(i=1;i<=n;i++){
		if(s[i]>0)now+=k,ans+=s[i]*k,q.push((node){s[i],2*k});
		else now-=k,ans-=s[i]*k;
		while(now>a[i]){
			node x=q.top();q.pop();
			if(x.v>now-a[i])x.v-=now-a[i],ans-=(now-a[i])*x.s,q.push(x),now=a[i];
			else now-=x.v,ans-=x.v*x.s;
		} 
	} 
	cout<<ans<<endl; 
	return 0;
}

P6024 机器人

微扰法+期望的好题

看到顺序,想到两数贪心(get新名词,这叫微扰法)。

那么考虑序列只有2个数x和y,x排在y前面的条件:

如果这是个期望dp,那么dp[i]=pi*(dp[i-1]+wi)+(1-pi)(dp[i-1]+dp[i]+wi),化简得dp[i]=(dp[i-1]+wi)/pi

不要着急写上去,因为浮点误差放到微扰法上的后果是很大的!
所以要尽量简化为整数形式!

然后假设只有2个数,即n=2,此时dp[2]=w1+w2p1

即要使w1+w2p1<w2+w1p2,再化简得w1(10000-p2)<w2(10000-p1)
这样就避免了浮点误差,妙啊!

再说一句,期望一定要勇(因为计算量极大,且无法检验、找规律,要尽量一遍写对)!!!

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=2e5+5;
struct node{
	int p,w,id;
}a[N];
int cmp(node x,node y){
	long long a1=1ll*x.w*(10000ll-1ll*y.p);
	long long a2=1ll*y.w*(10000ll-1ll*x.p);
	return a1<a2;
}
int main(){
	int i;
	cin>>n;
	for(i=1;i<=n;i++)scanf("%d",&a[i].w);
	int flag=0; 
	for(i=1;i<=n;i++)scanf("%d",&a[i].p),flag|=(!a[i].p),a[i].id=i;
	sort(a+1,a+n+1,cmp);
	if(flag)puts("Impossible");
	else{
		for(i=1;i<n;i++)printf("%d ",a[i].id);
		cout<<a[i].id<<endl;
	}
	return 0;
} 

P6034 Ryoku 与最初之人笔记

挺好的一道题,让我意识到了做数学题可以用数位dp。。。。

首先通过同余推出(a xor b)|(b-a)

因为异或是不退位的二进制减法,所以(a xor b)>=(b-a)

所以(a xor b)==(b-a),即a为b的子集

枚举b,答案转换成了 $ \sum\limits_{i=1}^{n} 2^{f(i)}$ (f(i)表示i在二进制下的1的个数)

然后枚举f(i),$ \sum\limits_{i=1}^{63} 2^i \times calc(n,i)$

calc(n,i)即<=n的数中二进制下有i个1的数的个数,用数位dp求出

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll n,ans,c[200],cnt;
const ll mod=1e9+7;
ll dp[200][2000];
ll dfs(ll pos,ll sum,ll ismax){
	if(pos==0) {
		if(sum==0)return 1;
		return 0;
	}
	if(!ismax&&~dp[pos][sum]){
		return dp[pos][sum];
	}
	ll dickup=ismax?c[pos]:1;
	ll ans=0;
	for(ll i=0;i<=dickup;i++){
		ans=(ans+dfs(pos-1,sum-i,ismax&&i==c[pos]))%mod;
	} 
	if(!ismax)dp[pos][sum]=ans;
	return ans; 
}
ll calc(ll x,ll k){
	cnt=0;
	memset(dp,-1,sizeof(dp));
	while(x){
		c[++cnt]=x%2;
		x/=2;
	}
	return dfs(cnt,k,1);
}
int main(){
	cin>>n;
	ll i;
	for(i=1;i<=63;i++)ans=(ans+((1ull<<i)%mod)*(calc(n,i)%mod)%mod)%mod;
	cout<<(ans+mod-n%mod)%mod<<endl;
	return 0;
} 

P6513 [QkOI#R1] Quark and Tree

换根dp懒得打了,口胡一下。。。(www别打我以后再也不这么做了!)

我也不知道这神仙换根怎么想到的,反正以后遇到难题各种方法都去想一遍吧!

我们可以发现如果把每个点分别当做根(未加边)的Σw[i]*dep[i] (记为sum)求出,再减去加边所产生的贡献就比较好求。

记加的边为(u,v),记环上每条边为(i,fa[i])(即不包括(u,v)这条边)。记t[i]为在新树上所有点中,离环上所有点中i为最近点的点权和。即t[i]=Σ[d[j]==i]*w[j] (d[j]表示环上离j最近的点)

也就是说sum-=Σt[i]*dep[i] (i∈环) (相当于的d[j]=i的点dep[j]全都减去dep[i])

而t[i]显然等于s[i]-s[j] (j为与i相连,在环上的儿子,s[i]表示i的子树和)。把-s[j]算到t[j]上,记p[j]=t[j]dep[j],于是Σp[j]=Σ[s[j]dep[j]-s[j]*dep[fa[j]]]=Σs[j]

现在题目就转化成了要找两条经过u的链,使得这两条链上的s[i]之和最大,这个也可以用换根dp维护!

(原来那个wi≥0的点是要先想到正解再写的。。。)
所以,很多题目要先推到正解发现写不了,再写暴力!!!

永远先手玩,再推,再想结论!!!


P6748 『MdOI R3』Fallen Lord

我太菜了www,cxy神仙可是全场EA。其实主要是当时比较脑抽,瞎猜了几个傻逼结论。。。

首先想到中位数相当于最多k-k/2-1条边比它大

dp的状态是可以自己改变的!保证无后效性就行!

f[u][0/1]:当(u,fa)比fa大/小(0,1) 时以u为根的子树中的边+(u,fa)的边的边权和最大是多少。

分四种情况分类讨论:

1.val(u,fa)>a[u]&&>a[fa]

2.val(u,fa)>a[u]&&<=a[fa]

3.val(u,fa)<=a[u]&&>a[fa]

4.val(u,fa)<=a[u]&&<=a[fa]

code:

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=5e5+5;
int cnt,h[N<<1],nxt[N<<1],to[N<<1];
void add(int x,int y){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y;
}
typedef long long ll;
ll f[N][2];
int a[N],deg[N],q[N];
int cmp(int x,int y){
	return (f[x][1]-f[x][0])>(f[y][1]-f[y][0]); 
}
void dfs(int u,int fa){
	int i;
	int tot=0;
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa)dfs(v,u);
	} 
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa)q[++tot]=v;
	}
	sort(q+1,q+tot+1,cmp);
	ll tmp=0;
	for(i=1;i<=tot;i++){
		int v=q[i];
		if(i<=deg[u])tmp+=f[v][1];
		else tmp+=f[v][0];
	}
	if(fa)tmp+=min(a[u],a[fa]);
	f[u][0]=max(f[u][0],tmp);
	tmp=0;
	if(a[u]>a[fa]){
		for(i=1;i<=tot;i++){
			int v=q[i];
			if(i<=deg[u])tmp+=f[v][1];
			else tmp+=f[v][0];
		}
		if(fa)tmp+=a[u];
		f[u][1]=max(f[u][1],tmp);
	}
	f[u][1]=max(f[u][1],f[u][0]);
	if(!deg[u])return ;
	tmp=0;
	if(a[fa]>a[u]){
		for(i=1;i<=tot;i++){
			int v=q[i];
			if(i<deg[u])tmp+=f[v][1];
			else tmp+=f[v][0];
		}
		if(fa)tmp+=a[fa];
		f[u][0]=max(f[u][0],tmp);
	}
	tmp=0;
	for(i=1;i<=tot;i++){
		int v=q[i];
		if(i<deg[u])tmp+=f[v][1];
		else tmp+=f[v][0];
	}
	if(fa)tmp+=m;
	f[u][1]=max(f[u][1],tmp);
	f[u][1]=max(f[u][1],f[u][0]);
} 
int main(){
	memset(f,0xcf,sizeof(f));
	int i;
	cin>>n>>m;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
		deg[x]++,deg[y]++;
	}
	for(i=1;i<=n;i++)deg[i]=deg[i]-deg[i]/2-1;a[0]=m;
	dfs(1,0);
	cout<<max(f[1][0],f[1][1])<<endl;
	return 0;
} 

P6560 [SBCOI2020] 时光的流逝

这道题不难。

博弈的题肯定要先dfs到最终状态,所以我们就可以建反图,从终点开始bfs,同时删边。对于(u,v)这条边,如果f[u]是必败态,那f[v]一定是必胜态。如果v的每条边都被删了,而f[v]还是0,那就是必败态。

关于有环:

当点v被第二次访问到时:

如果这个点已经被更新到必胜态,可以直接跳过。如果被更新到必败态,那u->v必然已经被删去,直接跳过。

所以,只有在f[v]!=0时,接受v的第二次访问。

另,开始时将重点和入度为0的点f[v]=-1,入队,因为要使与终点相连的点为必胜态。

code:

#include<bits/stdc++.h>
using namespace std;
int n,m,Q;
const int N=500005;
int cnt,h[N*2],nxt[N*2],to[N*2],f[N],ru[N],r[N];
queue<int> q;
void add(int x,int y){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y;
}
inline int read(){
	int s=0,ff=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')ff=-1;c=getchar();}
	while(c>='0'&&c<='9')s=(s<<1)+(s<<3)+(c^48),c=getchar();
	return s*ff;
}
int main(){
	n=read(),m=read(),Q=read();
	register int i,j;
	for(i=1;i<=m;++i){
		int y=read(),x=read();
		add(x,y);ru[y]++;
	}
	int st,ed,u,v;
	for(j=Q;j;--j){
		st=read(),ed=read();
		for(i=1;i<=n;++i){
			r[i]=ru[i];
			if(i==ed||!r[i])f[i]=-1,q.push(i);
			else f[i]=0;
		}
		while(!q.empty()){
			u=q.front();q.pop();
			for(i=h[u];i;i=nxt[i]){
				v=to[i];
				if(f[v]!=0)continue;
				r[v]--;
				if(f[u]==-1)f[v]=1,q.push(v);
				else if(r[v]==0){
					if(f[v]!=1)f[v]=-1;
					q.push(v);
				}
			}
		}
		printf("%d\n",f[st]);
	}
	return 0;
}

P6584 重拳出击

其实还是有点思维难度的,但是被我用简单的方法水了过去。

先以x为根建树,然后观察每个点到x距离的变化。暴力dfs从根往下走计算答案,发现在x子树中的点距离-1,其他点距离+1。每次更新答案,就是从这个点开始停止的答案(显然不会往回走,也不会在中间停):max(dep[u]+1,T.max-k+1) 这个用dfs序和线段树实现就行了。

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=400005;
int cnt,h[N*2],to[N*2],nxt[N*2],vis[N];
void add(int x,int y){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y;
}
struct node{
	int l,r,mx;
}t[N*4];
int tot,lz[N*4],in[N],out[N],c[N];
int dis[N],rt,len;
void dfs(int u,int fa){
	int i;
	dis[u]=dis[fa]+1;
	in[u]=++tot;
	c[tot]=u;
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa){
			dfs(v,u);
		}
	}
	out[u]=tot;
}
void update(int k){
	t[k].mx=max(t[k<<1].mx,t[k<<1|1].mx);
}
void build(int k,int l,int r){
	if(l==r){
		t[k].l=l,t[k].r=r;
		if(vis[c[l]])t[k].mx=dis[c[l]];
		return ;
	}
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	update(k);
}
void pushdown(int k){
	if(lz[k]){
		lz[k<<1]+=lz[k],lz[k<<1|1]+=lz[k];
		t[k<<1].mx+=lz[k],t[k<<1|1].mx+=lz[k];
		lz[k]=0;
	}
}
void add(int k,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){
		t[k].mx+=v;
		lz[k]+=v;
		return ;
	}
	pushdown(k);
	int mid=l+r>>1;
	if(R<=mid)add(k<<1,l,mid,L,R,v);
	else if(L>mid)add(k<<1|1,mid+1,r,L,R,v);
	else add(k<<1,l,mid,L,mid,v),add(k<<1|1,mid+1,r,mid+1,R,v);
	update(k);
}
int ans=1e9;
void dfs1(int u,int fa){
	int i;
	ans=min(ans,max(dis[u]+1,t[1].mx-len+1));
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa){
			add(1,1,n,1,n,1);
			add(1,1,n,in[v],out[v],-2);
			dfs1(v,u);
			add(1,1,n,1,n,-1);
			add(1,1,n,in[v],out[v],2);
		}
	}
}
int main(){
	int i;
	cin>>n;
	for(i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}
	cin>>m;
	for(i=1;i<=m;i++){
		int x;
		scanf("%d",&x);
		vis[x]=1;
	}
	cin>>len>>rt;
	dis[0]=-1;
	dfs(rt,0);
	build(1,1,n);
	dfs1(rt,0);
	cout<<ans<<endl;
	return 0;
}

P6592 [YsOI2020]幼儿园

先建反图,亲密程度在Li,Ri之间的边能将1,x联通,相当于1-x的所有路径中,必定存在一条路径上的最大边权<=R且最小边权>=L,两个条件不好搞。那就设f[v]为从1-v的最小边权最大值,对于第i条边(u,v),更新f[v],并在动态开点线段树第v颗树的f[v]位置设为i(即这条路径的最大边权)。

然后询问区间搞一下就好了。

#include<bits/stdc++.h>
using namespace std;
int n,m,q,w;
const int N=200005;
int f[N],rt[N],tot;
struct node{
	int val,lc,rc;
	node(){
		val=1e9;
	}
}t[N*30];
void modifly(int &k,int l,int r,int L,int R){
	if(!k)k=++tot,t[k].val=1e9;
	if(l==r){
		t[k].val=min(t[k].val,R);
		return ;
	}
	int mid=l+r>>1;
	if(L<=mid)modifly(t[k].lc,l,mid,L,R);
	else modifly(t[k].rc,mid+1,r,L,R);
	t[k].val=min(t[t[k].lc].val,t[t[k].rc].val);
}
int query(int k,int l,int r,int L,int R){
	if(!k||(L<=l&&r<=R))return t[k].val;
	int mid=l+r>>1;
	int ans=1e9;
	if(R<=mid)ans=min(ans,query(t[k].lc,l,mid,L,R));
	else if(L>mid)ans=min(ans,query(t[k].rc,mid+1,r,L,R));
	else ans=min(query(t[k].lc,l,mid,L,mid),query(t[k].rc,mid+1,r,mid+1,R));
	return ans;
}
int main(){
	int i;
	cin>>n>>m>>q>>w;
	memset(f,-1,sizeof(f));
	f[1]=0;
	for(i=1;i<=m;i++){
		int v,u;
		scanf("%d%d",&v,&u);
		if(~f[u]){
			f[v]=max(f[v],u==1?i:f[u]);
			modifly(rt[v],1,m,f[v],i);
		}
	}
	int lst=0;
	while(q--){
		int x,l,r;
		scanf("%d%d%d",&x,&l,&r);
		if(w)x^=lst,l^=lst,r^=lst;
		if(x==1){
			puts("1");lst++;
			continue;
		}
		int ans=query(rt[x],1,m,l,r);
		if(ans<=r)puts("1"),lst++;
		else puts("0");
	}
	return 0;
}

P1912 [NOI2009]诗人小G

emmmm其实就是决策单调性优化的裸题啦,最近准备敲敲板子(其实是被考试搞自闭了。。。)

很显然f[i]=min(f[j]+abs(sum[i]-sum[j]-l-1)^P)

然后打表发现是决策单调的。

一年前的我会这么写:

for(i=1;i<=n;i++){

for(j=p[i-1];j<i;j++){

...
        p[i]=...

}
}

CSPd2t2的时候我也是这么写的,可是不知道哪里挂了24分,后来发现复杂度根本就是错的。。。。

既然决策点是单调的,也就是说在i和i-1中,i后面的点肯定会更偏向于i。既然如此,对于每个决策点,以它为最优决策点的区间一定是连续的!所以我们要支持两个操作:1.二分pos,使得pos以前的点都选<i的点,且posi都选i。2.将posn赋值为i。

这就可以利用珂朵莉树的思想,用单调队列维护三元组(c,l,r)(c是决策点,[l,r]是其对应区间)。

1.队头删除决策:若r<i则while head++,最后得到r>=i的区间并将其l赋值为i(二分时减小常数)

2.队尾添加决策:若对于q[t].l来说i比q[t].c更优,则意味着[q[t].l,q[t].r]都是i更优(显然),while tail--。最后二分pos(pos定义如上),若pos存在(<=n)则在队尾新加一个node{i,pos,n}的节点,表示posn的点在1i中决策,i是1~i中最优的。

code:

#include<bits/stdc++.h>
using namespace std;
int T;
typedef long long ll;
const int N=1e5+5;
ll n,l,p;
long double s[N],f[N];
int lst[N];
char str[3*N][35];
int h,t,pr[N];
struct node{
  	int c,l,r;
}q[N];
long double qp(long double x,ll y){
  	long double res=1;
  	while(y){
  		if(y&1)res=(res*x);
  		x=(x*x);
  		y>>=1;
  	}
  	return res;
}
long double getdp(int i,int k){
  	return f[k]+qp(abs(s[i]-s[k]-l-1),p);
}
void bound(int y){
  	int l=q[t].l,r=q[t].r,c=q[t].c,pos=r+1;
  	while(l<=r){
  		int mid=l+r>>1;
  		if(getdp(mid,c)>=getdp(mid,y))pos=mid,r=mid-1;
  		else l=mid+1;
  	}
  	if(pos!=q[t].l)q[t].r=pos-1;
  	else t--;
  	if(pos<=n)t++,q[t].l=pos,q[t].r=n,q[t].c=y;
}
int nxt[N];
void output(){
    if(f[n]>1e18)puts("Too hard to arrange");
    else{
        printf("%lld\n",(ll)(f[n]+0.5));
        for(int i=n;i;i=pr[i])nxt[pr[i]]=i;
        int now=0;
        for(int i=1;i<=n;++i){
  		    now=nxt[now];
            for(int j=i;j<now;++j)printf("%s ",str[j]);
  			printf("%s\n",str[now]);
            i=now;
        }
    }
    puts("--------------------");
}
int main(){
  	int i,j;
  	cin>>T;
  	while(T--){
  		scanf("%lld%lld%lld",&n,&l,&p);
  		memset(f,0,sizeof(f));
  		memset(q,0,sizeof(q));
  		memset(pr,0,sizeof(pr));
  		memset(nxt,0,sizeof(nxt));
  		for(i=1;i<=n;i++){
  			scanf("%s",str[i]);
  			s[i]=s[i-1]+strlen(str[i])+1;
  		}//f[i]=f[k]+|s[i]-s[k]-l-1|^p
  		q[h=t=1]=(node){0,1,n};
  		for(i=1;i<=n;i++){
  			while(h<t&&q[h].r<i)h++;
  			q[h].l=i;
  			f[i]=getdp(i,q[h].c);pr[i]=q[h].c;
  			while(h<t&&getdp(q[t].l,i)<=getdp(q[t].l,q[t].c))t--;
  			bound(i);
  		}
  		output();
  	}
  	return 0;
}

CF149D 【Coloring Brackets】

嗯,读完一遍题发现很像CSPd1t2有木有,莫非?
好了回归正题qwq

所有的括号序列,都是形如(()()()(()()))(())(()())
很简单,只有两种形态,一是套上一个括号序列,二是与前面的括号序列相接。dp状态需要合并哦,这让我们想到了尘封已久的区间dp!

状态转移就可以推出来了哦,设f[l][r]表示l-r区间合法染色的种数,那么有两种情况(l-r默认是合法序列,这个可以通过记忆化搜索来保证)

1.l与r是一对匹配的括号,此时f[l][r]=(f[l+1][r-1])+限制

2.l与r不匹配,此时f[l][r]=f[l][p[l]]×f[p[l]+1][r]+限制(p[l]表示与l匹配的括号位置)

如果要比较方便地实现,建议再开两维分别表示l,r的颜色。

注意取模和记搜,完结撒花()!

#include<bits/stdc++.h>
using namespace std;
const int N=705;
char s[N];
typedef long long ll;
const ll mod=1e9+7;
int n;
ll f[N][N][3][3],fl[N][N];
int tot,st[N],q[N];
void dfs(int l,int r){
	int i,j,k,m;
	if(l>r){
		fl[l][r]=1;
		return ;
	}
	if(l==r){
		for(i=0;i<3;i++)f[l][r][i][i]=1;
		fl[l][r]=1;
		return ;
	}
	if(fl[l][r]){
		return ;
	}
	if(q[l]==r){
		if(l==r-1){
			f[l][r][0][1]=1;f[l][r][1][0]=1;
			f[l][r][0][2]=1;f[l][r][2][0]=1;
			fl[l][r]=1;
			return ;
		}
		dfs(l+1,r-1);
		f[l][r][0][1]=(f[l+1][r-1][0][0]+f[l+1][r-1][0][2]+f[l+1][r-1][1][0]
		+f[l+1][r-1][1][2]+f[l+1][r-1][2][0]+f[l+1][r-1][2][2])%mod;
		f[l][r][0][2]=(f[l+1][r-1][0][0]+f[l+1][r-1][0][1]+f[l+1][r-1][1][0]
		+f[l+1][r-1][1][1]+f[l+1][r-1][2][0]+f[l+1][r-1][2][1])%mod;
		f[l][r][1][0]=(f[l+1][r-1][0][0]+f[l+1][r-1][0][1]+f[l+1][r-1][0][2]
		+f[l+1][r-1][2][0]+f[l+1][r-1][2][1]+f[l+1][r-1][2][2])%mod; 
		f[l][r][2][0]=(f[l+1][r-1][0][0]+f[l+1][r-1][0][1]+f[l+1][r-1][0][2]
		+f[l+1][r-1][1][0]+f[l+1][r-1][1][1]+f[l+1][r-1][1][2])%mod; 
		fl[l][r]=1;
		return ;
	}else{
		dfs(l,q[l]),dfs(q[l]+1,r);
		for(i=0;i<3;i++){
			for(j=0;j<3;j++){
				for(k=0;k<3;k++){
					for(m=0;m<3;m++){
						if(k!=m||(k==0&&m==0)){
							f[l][r][i][j]=(f[l][r][i][j]+(f[l][q[l]][i][k]*f[q[l]+1][r][m][j])%mod)%mod;
						}
					}
				}
			}
		}
		fl[l][r]=1;
		return ;
	}
}
int main(){
	int i,j;
	scanf("%s",s+1);
	n=strlen(s+1);
	for(i=1;i<=n;i++){
		if(s[i]=='(')st[++tot]=i;
		else {
			int t=st[tot];tot--;
			q[i]=t,q[t]=i;
		} 
	}
	for(i=1;i<=n;i++)
	dfs(1,n);
	ll ans=0;
	for(i=0;i<3;i++)for(j=0;j<3;j++)ans=(ans+f[1][n][i][j])%mod;
	cout<<ans<<endl;
	return 0;
} 

P6583 回首过去

比赛时再次nc,没想出来。(竟去推莫反了。。。)

80分就是算Σn/(x/i)

正解就改一下,由整除分块枚举x/i(共sqrt(n)种),显然这些就是与2、5互质的,那就统计这些x/i共会造成多少贡献,也就是与多少p[i](只含2、5)乘起来<=n(设ans个),直接记录下来,排序统计。(而对于同一块内的数,这个ans是相等的,显然)

逆向思维真tm很重要啊!

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,tot,a[5005],ans;
ll cnt(ll x){
	return x-x/2-x/5+x/10;
}
int main(){
	cin>>n;
	ll i,j,l,r,k;
	for(i=1;i<=n;i*=2){
		for(j=1;j<=n/i;j*=5){
			a[++tot]=i*j;
		}
	}
	sort(a+1,a+tot+1);
	for(l=1;l<=n;l=r+1){
		k=n/l,r=n/(n/l);
		while(a[tot]>k&&tot)tot--;
		ans=ans+k*(cnt(r)-cnt(l-1))*tot;
	}
	cout<<ans<<endl;
	return 0;
}

posted @ 2023-07-14 14:18  Anticipator  阅读(22)  评论(0)    收藏  举报