hyy的練習賽總結
-
題目爲hyy大佬原創。
-
以下代碼默認開
hyy有魚系列(序章)
題解
顯然,這是一個圖論最短路的題目,建圖之後即可解決。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,m,w,h;
int a[100100],len=0,hea[100100];
struct node
{
int ne,to,l,fr;
}e[1600800];
inline void adde(int u,int v,int l)
{
e[++len].ne=hea[u];
hea[u]=len;
e[len].to=v;
e[len].l=l;;
}
struct noee
{
int u;
long long d;
bool operator <(const noee& rhs) const
{
return d>rhs.d;
}
};
int dis[100100];
priority_queue<noee> wq;
void dij(int s)
{
memset(dis,63,sizeof(dis));
dis[s]=0;
wq.push((noee){s,0});
while(!wq.empty())
{
noee fr=wq.top();
wq.pop();
int u=fr.u;
long long d=fr.d;
int i;
if(d!=dis[u]) continue;
i=hea[u];
while(i)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].l)
{
dis[v]=dis[u]+e[i].l;
wq.push((noee){v,dis[v]});
}
i=e[i].ne;
}
}
}
int main()
{
n=read();
int i=1;
while(i<=n)
{
scanf("%d",&a[i]);
if(i+a[i]<=n) adde(i,i+a[i],2);
if(i^n) adde(i,i+1,1);
if(i^1) adde(i,i-1,2);
++i;
}
dij(1);
printf("%d\n",dis[n]);
return 0;
}
hyy有魚系列(1)
題解
覆蓋整個區間,問最少使用數,這顯然是可以用來寫的。
我們設,表示以爲右端點的所有區間中,最小的左端點;設表示都被覆蓋的最小使用數。我們可以得到以下方程:
可以證明這是正確的。
唯一的問題是這麼做的複雜度是的,不過很多數據結構都支持求區間求最小值,這裏我使用的是線段樹。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,to[1000100],minn[4000400],m;
inline void build(int l,int r,int k)
{
if(l>r) return ;
if(l==r)
{
if(l==0) minn[k]=0;
else minn[k]=999999999;
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
minn[k]=min(minn[k<<1],minn[k<<1|1]);
}
inline int query(int l,int r,int k,int ll,int rr)
{
if(l>r) return 999999999;
if((l>=ll)&&(r<=rr)) return minn[k];
if((l>rr)||(r<ll)) return 999999999;
int mid=(l+r)>>1;
return min(query(l,mid,k<<1,ll,rr),query(mid+1,r,k<<1|1,ll,rr));
}
inline void change(int l,int r,int k,int x,int val)
{
if(l>r) return ;
if(l==r)
{
minn[k]=val;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,k<<1,x,val);
else change(mid+1,r,k<<1|1,x,val);
minn[k]=min(minn[k<<1],minn[k<<1|1]);
}
int main()
{
memset(to,63,sizeof(to));
n=read();
m=read();
int i=1;
while(i<=m)
{
int l,r;
l=read();
r=read();
--l;
to[r]=min(to[r],l);
++i;
}
build(0,n,1);
i=1;
while(i<=n)
{
int f=query(0,n,1,to[i],i-1);
change(0,n,1,i,f+1);
++i;
}
int ans=query(0,n,1,n,n);
if(ans>1000010) printf("-1\n");
else printf("%d\n",ans);
return 0;
}
當然
這種寫法雖然無腦,但是確實不怎麼巧妙。不過稍加思索之後,它可以轉化爲最短路的模型。
每一個區間都建一條邊,但是這樣的話,怎麼允許區間的覆蓋呢?很簡單,每個節點都向建一條邊即可。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,m,w,h;
int len=0,hea[1001001];
struct node
{
int ne,to,l,fr;
}e[2002002];
inline void adde(int u,int v,int l)
{
e[++len].ne=hea[u];
hea[u]=len;
e[len].to=v;
e[len].l=l;;
}
struct noee
{
int u;
long long d;
bool operator <(const noee& rhs) const
{
return d>rhs.d;
}
};
int dis[1001001];
priority_queue<noee> wq;
void dij(int s)
{
memset(dis,63,sizeof(dis));
dis[s]=0;
wq.push((noee){s,0});
while(!wq.empty())
{
noee fr=wq.top();
wq.pop();
int u=fr.u;
long long d=fr.d;
int i;
if(d!=dis[u]) continue;
i=hea[u];
while(i)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].l)
{
dis[v]=dis[u]+e[i].l;
wq.push((noee){v,dis[v]});
}
i=e[i].ne;
}
}
}
int main()
{
n=read();
m=read();
int i=1,l,r;
while(i<=m)
{
l=read();
r=read();
adde(l-1,r,1);
++i;
}
i=1;
while(i<=n)
{
adde(i,i-1,0);
++i;
}
dij(0);
if(dis[n]<999999999) printf("%d\n",dis[n]);
else printf("-1\n");
return 0;
}
hyy有魚系列(2)
題解
顯然是一個五維的完全揹包,也許有大佬可以使用狀態壓縮,但實際上裸的揹包也可以過這道題。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int f[21][21][21][21][21],m,a[21],b[21],c[21],d[21],e[21],aa,bb,cc,dd,ee,g[21];
int main()
{
aa=read();
bb=read();
cc=read();
dd=read();
ee=read();
m=read();
int i=1;
while(i<=m)
{
a[i]=read();
b[i]=read();
c[i]=read();
d[i]=read();
e[i]=read();
g[i]=read();
++i;
}
memset(f,0,sizeof(f));
int aaa=1,bbb=1,ccc=1,ddd=1,eee=1;
i=1;
while(i<=m)
{
aaa=a[i];
while(aaa<=aa)
{
bbb=b[i];
while(bbb<=bb)
{
ccc=c[i];
while(ccc<=cc)
{
ddd=d[i];
while(ddd<=dd)
{
eee=e[i];
while(eee<=ee)
{
f[aaa][bbb][ccc][ddd][eee]=max(f[aaa][bbb][ccc][ddd][eee],f[aaa-a[i]][bbb-b[i]][ccc-c[i]][ddd-d[i]][eee-e[i]]+g[i]);
++eee;
}
++ddd;
}
++ccc;
}
++bbb;
}
++aaa;
}
++i;
}
int ans=f[aa][bb][cc][dd][ee];
aaa=aa;
bbb=bb;
ccc=cc;
ddd=dd;
eee=ee;
while(1)
{
if(aa==0) break;
if(f[aa-1][bb][cc][dd][ee]^ans) break;
--aa;
}
while(1)
{
if(bb==0) break;
if(f[aa][bb-1][cc][dd][ee]^ans) break;
--bb;
}
while(1)
{
if(cc==0) break;
if(f[aa][bb][cc-1][dd][ee]^ans) break;
--cc;
}
while(1)
{
if(dd==0) break;
if(f[aa][bb][cc][dd-1][ee]^ans) break;
--dd;
}
while(1)
{
if(ee==0) break;
if(f[aa][bb][cc][dd][ee-1]^ans) break;
--ee;
}
printf("%d\n%d %d %d %d %d\n",ans,aaa-aa,bbb-bb,ccc-cc,ddd-dd,eee-ee);
return 0;
}
hyy有魚系列(3)
題解
一道還算不錯的題。
如果不看保質期,這道題就是個完全揹包。因爲每天喫的食物是互不影響的,所以只需要每天取最小的可以讓小魚喫飽的錢就行了。
而加上保質期,這個問題就變成了瞭如何將完全揹包中的某個物品刪除。這個問題無疑是非常困難的。所以不如倒着來,從最後一天開始,一個個將物品加入完全揹包。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int n,v,day=0,f[5050],val[3000300],mon,cost=0;
struct nobe
{
int c,t,d;
}a[5050];
inline bool mmp(nobe a,nobe b)
{
return a.t>b.t;
}
int main()
{
mon=read();
v=read();
n=read();
int i=1,j=n;
while(i<=n)
{
a[i].c=read();
a[i].t=read();
a[i].d=read();
day=max(day,a[i].t);
++i;
}
sort(a+1,a+n+1,mmp);
memset(f,63,sizeof(f));
f[0]=0;
i=day;
int now=1;
while(i>=1)
{
while((a[now].t>=i)&&(now<=n))
{
j=0;
while(j<=v+1)
{
if(f[j]<=9999999) f[min(v+1,j+a[now].d)]=min(f[min(v+1,j+a[now].d)],f[j]+a[now].c);
++j;
}
++now;
}
val[i]=min(f[v+1],f[v]);
--i;
}
i=1;
while(i<=day)
{
if(cost+val[i]<=mon) cost+=val[i];
else break;
++i;
}
printf("%d %d\n",i-1,mon-cost);
return 0;
}
hyy有魚系列(4)
題解
由於,所以每一行建一棵線段樹就可以了。
如果將變爲,二維線段樹可以解決一切問題。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int maxx[4040][1010],laz[4040][1010],c[1010][1010],n,m,tt;
inline void build(int l,int r,int k,int i)
{
if(l>r) return ;
laz[k][i]=0;
if(l==r)
{
maxx[k][i]=c[l][i];
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1,i);
build(mid+1,r,k<<1|1,i);
maxx[k][i]=max(maxx[k<<1][i],maxx[k<<1|1][i]);
}
inline void pushdown(int k,int i)
{
laz[k<<1][i]+=laz[k][i];
laz[k<<1|1][i]+=laz[k][i];
maxx[k<<1][i]+=laz[k][i];
maxx[k<<1|1][i]+=laz[k][i];
laz[k][i]=0;
}
inline void update(int l,int r,int k,int x,int ll,int rr,int i)
{
if((l>=ll)&&(r<=rr))
{
laz[k][i]+=x;
maxx[k][i]+=x;
return ;
}
if((l>rr)||(r<ll)) return ;
int mid=(l+r)>>1;
pushdown(k,i);
update(l,mid,k<<1,x,ll,rr,i);
update(mid+1,r,k<<1|1,x,ll,rr,i);
maxx[k][i]=-2147483648;
maxx[k][i]=max(maxx[k<<1][i],maxx[k<<1|1][i]);
}
inline int query(int l,int r,int k,int ll,int rr,int i)
{
if(l>r) return -2147483648;
if((l>=ll)&&(r<=rr)) return maxx[k][i];
if((l>rr)||(r<ll)) return -2147483648;
int mid=(l+r)>>1;
pushdown(k,i);
maxx[k][i]=max(maxx[k<<1][i],maxx[k<<1|1][i]);
return max(query(l,mid,k<<1,ll,rr,i),query(mid+1,r,k<<1|1,ll,rr,i));
}
int main()
{
n=read();
m=read();
register int i=1,j=1,k=1,l=1,q=1,x,ii=1,jj=1;
i=1;
while(i<=n)
{
j=1;
while(j<=m)
{
c[i][j]=read();
++j;
}
++i;
}
i=1;
while(i<=m)
{
build(1,n,1,i);
++i;
}
tt=read();
while(tt--)
{
q=read();
i=read();
j=read();
k=read();
l=read();
if(q==1)
{
x=read();
ii=j;
while(ii<=l)
{
update(1,n,1,x,i,k,ii);
++ii;
}
}
else
{
int ans=-2147483648;
ii=j;
while(ii<=l)
{
ans=max(ans,query(1,n,1,i,k,ii));
++ii;
}
printf("%d\n",ans);
}
}
return 0;
}
hyy有魚系列(5)
題解
乍一看很蒙,但是我們細細想一想就不是很蒙了。
我們先對一個二進制數考慮:
是不確定的數字,有位,已經確定的有個,從左往右第一個恆爲。
因爲要計算所有的1的總和,我們先考慮後面未知的部分。如果設某一位爲,其他的部分有種可能,所以這一部分的總和爲;接着考慮前面的部分。未知的部分可能性有種,所以是。
現在得出結論,設的二進制寫法中的寫法爲。
答案爲:
和剛纔的考慮方法類似,但是隻用考慮後面未知的部分
是不確定的數字,有位,已經確定的有個,從左往右第一個恆爲。
當有個不確定的數字爲時,所有的情況有種,顯然這一部分的結果爲,即可得出結論。
設的二進制寫法中的寫法爲。
答案爲:
通過一些優化可以減少一些複雜度。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int mod=100000007;
int ttt,c;
long long n,ans=0,f[70][70],cc[130];
long long pow2[70];
inline int bitcount(long long n)
{
register int c=0;
while(n)
{
n&=(n-1);
++c;
}
return c;
}
inline long long ppow(long long a,int b)
{
long long res=1;
while(b)
{
if(b&1) res=(res*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return res%mod;
}
signed main()
{
register int i=1,j,sum;
pow2[0]=1;
while(i<=52)
{
pow2[i]=pow2[i-1]*2ll;
++i;
}
f[0][0]=1;
i=1;
while(i<=52)
{
f[i][0]=f[i][i]=1;
j=1;
while(j<i)
{
f[i][j]=f[i-1][j]+f[i-1][j-1];
if(f[i][j]>=(mod-1)) f[i][j]-=mod-1;
++j;
}
++i;
}
ttt=read();
while(ttt--)
{
c=read();
scanf("%lld",&n);
if(c==1)
{
ans=0;
i=51;
while((i!=0)&&((n>>(i-1))&1)==0) --i;
sum=0;
while(i)
{
ans+=pow2[i-1]*sum;
ans%=mod;
if(i^1) ans+=pow2[i-2]*(i-1);
ans%=mod;
++sum;
--i;
while((i>0)&&((n>>(i-1))&1)==0) --i;
}
ans+=bitcount(n);
if(ans>=mod) ans-=mod;
printf("%lld\n",ans);
}
else
{
ans=1;
i=51;
memset(cc,0,sizeof(cc));
while((i)&&((n>>(i-1))&1)==0) --i;
sum=0;
while(i)
{
j=(sum==0) ? 1 : 0;
while(j<i)
{
cc[j+sum]+=f[i-1][j];
cc[j+sum]%=(mod-1);
++j;
}
++sum;
--i;
while((i)&&((n>>(i-1))&1)==0) --i;
}
i=1;
while(cc[i])
{
ans*=ppow(i,cc[i]);
ans%=mod;
++i;
}
ans*=bitcount(n);
ans%=mod;
printf("%lld\n",ans);
}
}
return 0;
}
hyy有魚系列(5)
題解
展開這個式子,就會得到這樣一個形式的東西:
是一個等差數列與一個冪的和,這兩個都有的做法。
設,有
證明略
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
#define int long long
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
int m,bb;
int a,b,mod,n;
long long qpow(long long a,long long b)
{
long long r=1,base=a;
while(b)
{
if(b&1) r=(r*base)%mod;
base=(base*base)%mod;
b>>=1;
}
return r;
}
long long sum(long long p,long long c)
{
if(c==0) return 1ll;
if(c==1) return p%mod+1ll;
if(c&1) return ((1ll+qpow(p,(c+1ll)/2ll))*sum(p,(c-1ll)/2ll))%mod;
else return ((1ll+qpow(p,c/2))*sum(p,c/2ll-1ll)+qpow(p,c))%mod;
}
signed main()
{
int a,b;
a=read();
b=read();
mod=read();
n=read();
if(n==1)
{
printf("1\n");
return 0;
}
int res=1;
res*=sum(a,n-2)%mod;
res%=mod;
res*=b;
res%=mod;
res+=qpow(a,n-1);
res%=mod;
cout<<res<<endl;
return 0;
}
hyy有魚系列(7)
題解
在下巨快。
?啊!
#include<bits/stdc++.h>
using namespace std;
int m,next[5050];
string s,s2;
void getnext(string p)
{
int plen=p.length();
next[0]=-1;
int k=-1;
int j=0;
while(j<plen-1)
{
if((k==-1)||(p[j]==p[k]))
{
++j;
++k;
if(p[j]!=p[k]) next[j]=k;
else next[j]=next[k];
}
else
{
k=next[k];
}
}
}
int kmpsearch(string s, string p)
{
int i=0;
int j=0;
int slen=s.length();
int plen=p.length();
while((i<slen)&&(j<plen))
{
if((j==-1)||(s[i]==p[j]))
{
++i;
++j;
}
else j=next[j];
}
if(j==plen) return i-j;
else return -1;
}
int main()
{
scanf("%d",&m);
cin>>s;
int cz,p,len;
while(m--)
{
scanf("%d",&cz);
if(cz==4) cout<<s<<endl;
else if(cz==2)
{
scanf("%d%d",&p,&len);
s.erase(p-1,len);
}
else if(cz==1)
{
scanf("%d",&p);
cin>>s2;
s.insert(p,s2);
}
else
{
cin>>s2;
getnext(s2);
if(kmpsearch(s,s2)!=-1) printf("yes\n");
else printf("no\n");
}
}
return 0;
}
hyy有魚系列(8)
題解
先考慮的情況,表示第個數字。這時矩陣變成區間。
首先:
- 一定是波瀾區間。
- 是波瀾區間的要求是任意,是波瀾區間,是波瀾區間,且。
正確性是顯然的。這兩條使我們可以用線段樹維護。
時呢?容易想到建棵線段樹,但是這樣複雜度是過不去的 (我卡過9九點)。實際上狀態壓縮,用二進制表示就可以了。
#include<bits/stdc++.h>
using namespace std;
char buf[1<<20],*fs,*ft;
inline int read()
{int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
long long is[400001],maxl,a[100001];
int n,m,q;
inline void pushup(int l,int r,int k)
{
int mid=(l+r)>>1;
long long aa=a[mid]^a[mid+1],ss=is[k<<1]&is[k<<1|1];
is[k]=aa&ss;
}
inline void build(int l,int r,int k)
{
if(l==r)
{
is[k]=(1ll<<(m))-1ll;
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
pushup(l,r,k);
}
inline void change(int l,int r,int k,int x)
{
if(l==r) return ;
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,k<<1,x);
else change(mid+1,r,k<<1|1,x);
pushup(l,r,k);
}
inline long long query(int l,int r,int k,int ll,int rr,int x,int len)
{
if((ll<=l)&&(rr>=r))
{
return ((is[k]>>(x-1))&((1ll<<(len))-1));
}
if((r<ll)||(l>rr)) return (1ll<<(len))-1;
int mid=(l+r)>>1;
long long f1=query(l,mid,k<<1,ll,rr,x,len),f2=query(mid+1,r,k<<1|1,ll,rr,x,len),aa,aa1,aa2;
if(mid+1>rr) return f1;
if(mid<ll) return f2;
aa1=((a[mid]>>(x-1))&((1ll<<(len))-1ll));
aa2=((a[mid+1]>>(x-1))&((1ll<<(len))-1ll));
aa=aa1^aa2;
return ((f1&f2)&aa);
}
int main()
{
n=read();
m=read();
q=read();
maxl=(1ll<<(m+1))-1;
register int i=1,j=1,k,l,cz,ii;
bool f;
while(i<=n)
{
j=1;
while(j<=m)
{
a[i]|=(1ll*read())<<(j-1);
++j;
}
++i;
}
build(1,n,1);
while(q--)
{
cz=read();
if(cz)
{
i=read();
j=read();
k=read();
l=read();
ii=j;
if(query(1,n,1,i,k,j,l-j+1)) printf("1\n");
else printf("0\n");
}
else
{
i=read();
j=1;
a[i]=0;
while(j<=m)
{
a[i]|=(1ll*read())<<(j-1);
++j;
}
change(1,n,1,i);
}
}
return 0;
}
最終總結
orzhyy
比賽的時候第六題沒有優化,結果沒有
個人覺得這一堆題還是不錯的,不過我太菜了就是。