寻宝游戏
毒瘤题。
估计考试只会前 30 p t s 30pts 30pts暴力然后果断走人。
正解是考虑到一个数 & 1 \&1 &1和 ∣ 0 |0 ∣0都没有变化, & 0 \&0 &0会强制变成 0 0 0, ∣ 1 |1 ∣1会强制变成 1 1 1,于是如果结果是 1 1 1说明最后一个出现的 ∣ 1 |1 ∣1在最后一个出现的 & 0 \&0 &0的后面,这样我们将所有的操作集合拿来搞成一个 01 01 01串并把所有 01 01 01串反向之后就可以求出可行范围然后得出答案了。
代码:
#include
#define ri register int
using namespace std;
typedef long long ll;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
return ans;
}
const int mod=1e9+7,N=5005;
int n,m,q,Bit[N],sum[N],c[2],rk[N],lastrk[N],l,r;
char s[N];
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline void update(int&a,const int&b){a=add(a,b);}
inline void init(){
for(ri i=Bit[0]=1;i<=n;++i)Bit[i]=(Bit[i-1]<<1)%mod;
for(ri i=1;i<=m;++i)rk[i]=i;
}
inline void Sort(){
for(ri i=c[0]=c[1]=0;i<n;++i,c[0]=c[1]=0){
scanf("%s",s+1);
for(ri j=1;j<=m;++j)++c[s[j]^48],update(sum[j],(s[j]^48)*Bit[i]);
c[1]+=c[0];
for(ri j=m;j;--j)lastrk[c[s[rk[j]]^48]--]=rk[j];
swap(lastrk,rk);
}
}
int main(){
n=read(),m=read(),q=read(),init(),Sort();
while(q--){
scanf("%s",s+1),l=0,r=m+1;
for(ri i=1;i<=m;++i)if(s[rk[i]]^48){r=i;break;}
for(ri i=m;i;--i)if(!(s[rk[i]]^48)){l=i;break;}
l>=r?cout<<0<<'\n':cout<<(dec(r>m?Bit[n]:sum[rk[r]],sum[rk[l]]))<<'\n';
}
return 0;
}
转盘
考虑时间倒流转化问题:
想象成从 T T T时刻开始每个时刻可以倒着走一步或者停住,每个物品有一个消失时间,要在所有物品消失之前经过它们。
为了方便我们断环为链
假设是从 i i i开始倒退 ( n ≤ i < 2 n ) (n\le i<2n) (n≤i<2n),则有 T − ( i − j ) ≥ t j ⇒ T ≥ ( t j − j ) + i ⇒ T m i n = max { T j − j } + i T-(i-j)\ge t_j\Rightarrow T\ge(t_j-j)+i\Rightarrow T_{min}=\max\{T_j-j\}+i T−(i−j)≥tj⇒T≥(tj−j)+i⇒Tmin=max{Tj−j}+i。
令 a i = t i − i a_i=t_i-i ai=ti−i
那么 A n s = m i n n ≤ i < 2 n { m a x i − n < j ≤ i { a j } + i } Ans=min_{n\le i<2n}\{max_{i-n<j\le i}\{a_j\}+i\} Ans=minn≤i<2n{maxi−n<j≤i{aj}+i}
因为 a i > a i + n , i ≤ n a_i>a_{i+n},i\le n ai>ai+n,i≤n
所以 A n s = m i n 1 ≤ i ≤ n { m a x i ≤ j ≤ 2 n { a j } + i } + n − 1 Ans=min_{1\le i\le n}\{max_{i\le j\le 2n}\{a_j\}+i\}+n-1 Ans=min1≤i≤n{maxi≤j≤2n{aj}+i}+n−1
然后发现就是唯一一个后缀 m a x max max的 m i n min min,这个可以用线段树维护单调栈实现。
代码:
#include
#define ri register int
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
return ans;
}
const int N=1e5+5,inf=0x3f3f3f3f;
int n,m,lastans,a[N<<1],type;
namespace SGT{
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (T[p].l+T[p].r>>1)
struct Node{int l,r,val,mx;}T[N<<3];
inline int query(int p,int lim,int v){
if(T[p].l==T[p].r)return min(T[p].l==lim?inf:T[p].l+1+v,T[p].l+max(v,T[p].mx));
if(v>=T[rc].mx)return query(lc,lim,v);
return min(T[p].val,query(rc,lim,v));
}
inline void pushup(int p){T[p].mx=max(T[lc].mx,T[rc].mx),T[p].val=query(lc,mid,T[rc].mx);}
inline void build(int p,int l,int r){
T[p].l=l,T[p].r=r;
if(l==r){T[p].mx=a[l];return;}
build(lc,l,mid),build(rc,mid+1,r),pushup(p);
}
inline void update(int p,int k,int v){
if(T[p].l==T[p].r){T[p].mx=v;return;}
update(k<=mid?lc:rc,k,v),pushup(p);
}
#undef lc
#undef rc
#undef mid
}
int main(){
n=read(),m=read(),type=read();
for(ri i=1;i<=n;++i)a[i]=read()-i,a[i+n]=a[i]-n;
SGT::build(1,1,n<<1);
cout<<(lastans=SGT::T[1].val+n-1)<<'\n';
for(ri x,y;m;--m){
x=read()^(type*lastans),y=read()^(type*lastans);
SGT::update(1,x,y-x),SGT::update(1,x+n,y-x-n);
cout<<(lastans=SGT::T[1].val+n-1)<<'\n';
}
return 0;
}
毒瘤
枚举边的状态+树形 d p dp dp这种暴力 75 75 75应该都是一眼会吧。
但这样会 T L E TLE TLE掉最后几个点 出题人毒瘤
然后需要建出这棵树的虚树来优化每次 d p dp dp的时间。
然后就没了。
代码:
#include
#define ri register int
#define fi first
#define se second
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
return ans;
}
typedef pair<int,int> pii;
typedef long long ll;
const int N=1e5+5,mod=998244353;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
int P[N][2],n,m,ans=0,f[N][2],siz[N],tot=0,dfn[N];
vector<int>e[N];
vector<pii>G;
bool vis[N],key[N],ban[N][2];
void Dfs(int p,int fa){
dfn[p]=++tot;
for(ri i=0,v;i<e[p].size();++i){
if((v=e[p][i])==fa)continue;
if(dfn[v]){key[p]=1;if(dfn[p]<dfn[v])G.push_back(pii(p,v));continue;}
else Dfs(v,p),siz[p]+=siz[v];
}
key[p]|=siz[p]>=2;
siz[p]=siz[p]||key[p];
}
struct Coef{
int x,y;
Coef(int _x=0,int _y=0):x(_x),y(_y){}
friend inline Coef operator+(const Coef&a,const Coef&b){return Coef(add(a.x,b.x),add(a.y,b.y));}
friend inline Coef operator*(const Coef&a,const int&b){return Coef(mul(a.x,b),mul(a.y,b));}
}k[N][2];
struct Node{int v;Coef a,b;};
vector<Node>E[N];
inline int Build(int p){
P[p][0]=P[p][1]=vis[p]=1;
int ret=0;
for(ri w,i=0,v;i<e[p].size();++i){
if(vis[v=e[p][i]])continue;
w=Build(v);
if(!w)P[p][1]=mul(P[p][1],P[v][0]),P[p][0]=mul(P[p][0],add(P[v][0],P[v][1]));
else if(key[p])E[p].push_back((Node){w,k[v][0]+k[v][1],k[v][0]});
else k[p][0]=k[v][0]+k[v][1],k[p][1]=k[v][0],ret=w;
}
if(key[p])k[p][0]=Coef(1,0),k[p][1]=Coef(0,1),ret=p;
else k[p][0]=k[p][0]*P[p][0],k[p][1]=k[p][1]*P[p][1];
return ret;
}
inline void solve(int p){
f[p][0]=ban[p][0]?0:P[p][0];
f[p][1]=ban[p][1]?0:P[p][1];
for(ri i=0,v;i<E[p].size();++i){
solve((v=E[p][i].v));
f[p][0]=mul(f[p][0],add(mul(E[p][i].a.x,f[v][0]),mul(E[p][i].a.y,f[v][1])));
f[p][1]=mul(f[p][1],add(mul(E[p][i].b.x,f[v][0]),mul(E[p][i].b.y,f[v][1])));
}
}
inline void init(int sta){
for(ri i=0,x,y,tmp;i<G.size();++i){
tmp=(sta>>i)&1,x=G[i].fi,y=G[i].se;
if(!tmp)ban[x][1]=1;
else ban[x][0]=ban[y][1]=1;
}
solve(1),ans=add(ans,add(f[1][0],f[1][1]));
for(ri i=0,x,y,tmp;i<G.size();++i){
tmp=(sta>>i)&1,x=G[i].fi,y=G[i].se;
if(!tmp)ban[x][1]=0;
else ban[x][0]=ban[y][1]=0;
}
}
int main(){
n=read(),m=read();
for(ri i=1,u,v;i<=m;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
Dfs(1,0),key[1]=1,Build(1);
for(ri sta=0,up=1<<G.size();sta<up;++sta)init(sta);
cout<<ans;
return 0;
}
游戏
对于两个可以不能相通的块,如果钥匙在左边,那么可能可以从左到右但并不能从右到左,我们对于这种情况建一条有向边,然后动态维护一个拓扑排序即可。
代码:
#include
#define ri register int
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
const int N=1e6+5;
int n,m,q,goa[N],l[N],r[N],mp[N],du[N],tot=0,x,y;
vector<int>e[N];
inline void add(const int&u,const int&v){e[u].push_back(v),++du[v];}
inline void extend(int i){
static int t1,t2;
while(1){
t1=l[i]^1,t2=r[i]^n;
if(t1)(!goa[l[i]-1])||(goa[l[i]-1]>=l[i]&&goa[l[i]-1]<=r[i])?l[i]=l[mp[l[i]-1]]:t1=0;
if(t2)(!goa[r[i]])||(goa[r[i]]>=l[i]&&goa[r[i]]<=r[i])?r[i]=r[mp[r[i]+1]]:t2=0;
if(!(t1+t2))break;
}
}
inline void topsort(){
static int q[N],hd,tl;
hd=1,tl=0;
for(ri i=1;i<=n;i++)if(mp[i]==i&&!du[i])q[++tl]=i;
while(hd<=tl){
int p=q[hd++];
extend(p);
for(ri i=0,v;i<e[p].size();++i)if(!(--du[v=e[p][i]]))q[++tl]=v;
}
}
int main(){
n=read(),m=read(),q=read();
for(ri i=1;i<=n;i++)mp[i]=l[i]=r[i]=i;
while(m--)x=read(),goa[x]=read();
for(ri i=1;i<=n;i++)if(!goa[i])mp[i+1]=mp[i];else goa[i]<=i?add(mp[i+1],mp[i]):add(mp[i],mp[i+1]);
for(ri i=1;i<=n;i++)l[mp[i]]=min(l[mp[i]],l[i]),r[mp[i]]=max(r[mp[i]],r[i]);
topsort();
for(ri i=1;i<=n;i++)l[i]=l[mp[i]],r[i]=r[mp[i]];
while(q--)x=read(),y=read(),puts(l[x]<=y&&y<=r[x]?"YES":"NO");
return 0;
}
排列
考虑按照题意建边建出来是一棵外向树。
然后显然应该按某一种拓扑序来形成序列(注意这个时候要判环来看是否合法)
这个东西可以用并查集+堆来搞一下,比较的依据是一个连通块的平均权值。
代码:
#include
#define ri register int
#define fi first
#define se second
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
return ans;
}
const int N=5e5+5;
typedef long long ll;
typedef pair<long double,int> pii;
int n,m,siz[N],fa[N],anc[N];
ll w[N],ans=0;
inline int find(const int&x){return x^anc[x]?anc[x]=find(anc[x]):x;}
struct In_Out_queue{
priority_queue<pii,vector<pii>,greater<pii> >a,b;
inline void push(const pii&x){a.push(x);}
inline void del(const pii&x){b.push(x);}
inline pii top(){while(b.size()&&a.top()==b.top())a.pop(),b.pop();return a.top();}
inline void pop(){while(b.size()&&a.top()==b.top())a.pop(),b.pop();a.pop();}
}q;
int main(){
n=read();
for(ri i=0;i<=n;++i)anc[i]=i;
for(ri i=1;i<=n;++i){
fa[i]=read();
if(anc[find(i)]^anc[find(fa[i])])anc[find(i)]=find(fa[i]);
else return puts("-1"),0;
}
ll ans=0;
anc[0]=0;
for(ri i=1;i<=n;++i)ans+=(w[i]=read()),anc[i]=i,siz[i]=1,q.push(pii((long double)w[i],i));
for(ri i=1;i<=n;++i){
int x=q.top().se,y=find(fa[x]);
q.pop();
if(y)q.del(pii((long double)w[y]/siz[y],y));
ans+=(ll)siz[y]*w[x],siz[y]+=siz[x],anc[x]=y,w[y]+=w[x];
if(y)q.push(pii((long double)w[y]/siz[y],y));
}
cout<<ans;
return 0;
}
道路
HNOI2018最水的一道没有之一没错就是它。 并不是反话
由于题目中给了深度保证,于是直接暴力三维树形 d p dp dp就能过了。
记忆化搜索真的好写
代码:
#include
#define ri register int
using namespace std;
typedef long long ll;
inline int read(){
int ans=0;
bool f=1;
char ch=getchar();
while(!isdigit(ch))f^=ch=='-',ch=getchar();
while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
return f?ans:-ans;
}
const int N=20005;
ll a[N],b[N],c[N],f[N][45][45];
int son[N][2],n;
ll dfs(int p,int x,int y){
if(p>=n)return c[p-n+1]*(a[p-n+1]+x)*(b[p-n+1]+y);
if(f[p][x][y]^f[0][0][0])return f[p][x][y];
return f[p][x][y]=min(dfs(son[p][0],x,y)+dfs(son[p][1],x,y+1),dfs(son[p][0],x+1,y)+dfs(son[p][1],x,y));
}
int main(){
n=read();
memset(f,127,sizeof(f));
for(ri i=1,x,y;i<n;++i){
x=read(),y=read();
if(x<0)x=n-x-1;
if(y<0)y=n-y-1;
son[i][0]=x,son[i][1]=y;
}
for(ri i=1;i<=n;++i)a[i]=read(),b[i]=read(),c[i]=read();
return cout<<dfs(1,0,0),0;
}