初二上 合集
开学计划 & 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;
}

浙公网安备 33010602011771号