几天没打博客了,现在记录一下一些比较好的题目。
树
Description
有n个点,它们从1到n进行标号,第i个点的限制为度数不能超过A[i].
现在对于每个s (1 <= s <= n),问从这n个点中选出一些点组成大小为s的有标号无根树的方案数。
n<=100
样例是
3
2 2 1
答案
3 3 2
solution
这一类的题目一般都会用到prufer编码,一个有x节点的数会变成一个有x-2节点的序列,每个点出现的次数是度数减一,然后我们的问题就变成了n个数里选x-2个数,i<=a[i]的方案数
4重循环爆不了,组合数杨辉三角形预处理即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#define N 107
using namespace std;
const long long mod=1000000007;
int t,n,a[N];
long long c[N][N],f[N][N][N];
int main(){
c[0][0]=1;
for(int i=1;i<=101;i++){
c[i][0]=1;
for(int j=1;j<=i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
scanf("%d",&t);
while(t--){
memset(f,0,sizeof(f));
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
for(int k=0;k<=n-2;k++){
f[i][j][k]=f[i-1][j][k];
if(j>0){
for(int l=1;l<=min(a[i]-1,k);l++)
f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k-l]*c[k][l]%mod)%mod;
}
}
printf("%d ",n);
for(int i=2;i<=n;i++){
long long ans=0;
for(int j=0;j<=i-2;j++)
ans=(ans+f[n][j][i-2]*c[n-j][i-j])%mod;
printf("%lld ",ans);
}
printf("\n");
}
}
电话线铺设
Description
input
output
数据
solution
打比赛时跑去监考了,emm,看着四年级xxs考试真爽
由于李牌只要一个,所以说我们考虑先求王牌的最小生成树,然后枚举李牌(u,v,w),寻找王牌最小生成树上u与v的路径上权值最大的一条边,考虑把它换下来,然后记录最小值以及被换下的边就行了。
然后注意到用王牌不一定能联通,那么我们特判一下,枚举李牌看能不能把没联通的两部分联通,然后同理找最小值。
很多人说这题很烦,我倒是觉得没有啊,反正呢这题就是个最大生成树(显然kruskar适合点,毕竟特判时要判断联通部分)+树上路径(这不就是lca吗),lca打个倍增,没什么难的,注意细节。
另外真的很长,2800bites
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define N 400007
using namespace std;
int n,w,l,res,np,cnt,ans,fa[N],head[N],dis[N];
int f[N][30],mx[N][30],num[N][30];//倍增,分别表示父亲,最大边权,最大边权的编号
bool vis[N];
struct node{int u,v,w,num;}e[N];//求最小生成树
struct tree{int to,w,nxt,num;}tr[N<<1];//求完最小生成树用的
bool cmp(node a,node b){return a.w<b.w;}
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
int read(){//快读
char ch=getchar();int a=0;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){
a=a*10+(ch-'0');
ch=getchar();
}
return a;
}
void add(int u,int v,int w,int num){//链式前向星
tr[++cnt].to=v;
tr[cnt].w=w;
tr[cnt].num=num;
tr[cnt].nxt=head[u];
head[u]=cnt;
}
bool kruskal(){//最小生成树
int tot=0;
sort(e+1,e+w+1,cmp);//边排序
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=w;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
int fu=get(u),fv=get(v);//找祖宗
if(fu!=fv){//不连通
fa[fu]=fv;
ans+=w;
vis[e[i].num]=1;//说明这条边用过了
tot++;
add(u,v,w,e[i].num);//由于这条边被加入最小生成树中
add(v,u,w,e[i].num);//所以我们将它建边
}
if(tot==n-1) return 1;//联通了
}
return 0;//若到了这里说明没有联通
}
void dfs(int x,int fa){//我们找到每个点的深度,以及父亲,边权最大值及编号先处理好
dis[x]=dis[fa]+1;//深度
f[x][0]=fa;//父亲找到
for(int i=head[x];i;i=tr[i].nxt){
int v=tr[i].to;
if(v==fa) continue;
mx[v][0]=tr[i].w;
num[v][0]=tr[i].num;//由于v与x只有一条边,记录它的值与编号
dfs(v,x);
}
}
void RMQ(){
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
if(mx[i][j-1]>mx[f[i][j-1]][j-1])
mx[i][j]=mx[i][j-1],num[i][j]=num[i][j-1];
else mx[i][j]=mx[f[i][j-1]][j-1],num[i][j]=num[f[i][j-1]][j-1];
}
//以上不想讲,会倍增的自然懂
}
void find(int x,int y){//找lca,res,np记录最大值以及其编号,不讲
if(dis[x]<dis[y]) swap(x,y);
if(dis[x]>dis[y]){
for(int i=20;i>=0;i--)
if(dis[f[x][i]]>dis[y]){
if(mx[x][i]>res) res=mx[x][i],np=num[x][i];
x=f[x][i];
}
if(mx[x][0]>res) res=mx[x][0],np=num[x][0];
x=f[x][0];
}
if(x==y) return;
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
if(res<mx[x][i]) res=mx[x][i],np=num[x][i];
if(res<mx[y][i]) res=mx[y][i],np=num[y][i];
x=f[x][i],y=f[y][i];
}
}
if(mx[x][0]>res) res=mx[x][0],np=num[x][0];
if(mx[y][0]>res) res=mx[y][0],np=num[y][0];
return;
}
int main(){
freopen("telephone.in","r",stdin);
freopen("telephone.out","w",stdout);
n=read(),w=read(),l=read();
for(int i=1;i<=w;i++){
int u=read(),v=read(),val=read();
e[i].u=u;
e[i].v=v;
e[i].w=val;
e[i].num=i;//边的编号
}
if(kruskal()){//联通的话
dfs(1,0);//深搜一遍
RMQ();//跑倍增
int mn=0x3f3f3f3f,np1,np2=0;
//mn表示换上去的李牌与换下来的王牌差最小,np1表示换下来王牌的编号,np2表示换上去李牌的编号
for(int i=1;i<=l;i++){
int u=read(),v=read(),w=read();
res=0;
find(u,v);
if(w-res<mn)
mn=w-res,np1=np,np2=i;//np,res是本次find中找到的最大王牌边的编号以及权值
}
ans+=mn;
printf("%d\n",ans);
vis[np1]=false;//替下的王牌标记没有被选
for(int i=1;i<=w;i++)
if(vis[i]) printf("%d\n",i);
printf("%d\n",np2);
}else{//图不连通
int mn=0x3f3f3f3f,np2=0;
for(int i=1;i<=l;i++){
int u=read(),v=read(),w=read();
int fu=get(u),fv=get(v);
if(fu!=fv)//判断李牌的两个点是不是不连通,若不是的话,更新最小值
if(mn>w) mn=w,np2=i;
}
printf("%d\n",ans+mn);
for(int i=1;i<=w;i++)
if(vis[i]) printf("%d\n",i);
printf("%d\n",np2);
}
}
细节较多,要注意。
我的天
Description
很久很以前,有一个古老的村庄——xiba村,村子里生活着n+1个村民,但由于历届村长恐怖而且黑暗的魔法统治下,村民们各自过着独立的生活,完全没有意识到其他n个人的存在。
但有一天,村民xiba臻无意中也得到了魔法,并发现了这个恐怖的事实。为了反抗村长,他走遍了全世界,找到了其他n个村民,并组织他们发动革命。但让这n个素不相识的村民(xiba臻已跟他们认识)同心协力去抵抗村长是很困难的,所以xiba臻决定先让他们互相认识。
这里,xiba臻用了xiba村特有的xiba思维:先让这n个人排成一列,并依次从1-n标号。然后每次xiba臻会选出一个区间[l, r],在这个区间中的人会去认识其他在这个区间中的人,但已经认识过得不会再去认识。这样,进行m次操作后,xiba臻认为这n个人能认识到许多人。
但是,为了精确地知道当前有多少对人已经认识了,xiba臻想要知道每次操作后会新产生出多少对认识的人,但这已是xiba思维无法解决的事了,你能帮帮他吗?
输入
5 5
2 3
2 4
3 5
1 5
2 4
输出
1
2
2
5
0
数据
对于20%的数据,1≤n,m≤100。
对于50%的数据,1≤n,m≤5000。
对于100%的数据,1≤n,m≤300000,1≤li≤ri≤n。
solution
首先50分做法,对于i他认识的人,一定i+1到一个距离(这里只考虑他右边,因为他左边的答案前面的人会统计),然后这个距离就是每次包含i的询问的最大的r,也就是说设右边最远到d[i],那么一次询问l<=i<=r,d[i]=max(d[i],r) 然后通过这个累计答案即可。
对于一次询问,i的贡献就是r-d[i],若di本来就大于r就不加贡献(这个十分显然)
然后100分的话,我们就是要考虑如果快速维护区间取max了。
然后这个东西可以用吉司机线段树来做。O(nlogn)
但是很显然我不会这个东西。。。。。。
但是也并不是说不可做对吧???
这个也是可以用普通线段树,当然有很多方法,我这里就说我的想法。
首先维护每个区间最小值,最大值,总和。
考试时我没想那么多,只维护了总和。
是因为这样的,假设目前区间(被l,r包含)d[i]总和为sum,答案就是(区间个数*r)-sum对吧?然后我一开始也是这么想的,结果对拍时发现wa了。
为什么呢?因为我们的某个d[i]也许大于r,那么如50分做法我们是不累计答案的,但是在这里我们还是算了进去就会导致答案变少。
然后考试时我也没什么时间导致心态炸了也就不知道怎么优化了。
那么后面想了想维护上述的三个值就能搞定,但是速度会很慢,听别人说是nlog方,我也不太清楚,不过还是可以过的。
具体思想就是,若是原先,当目前区间被l,r包含我们就不用下传直接询问,修改。但是由于d[i]>r的存在会WA。于是我们现在加强标准,不仅要被包含,而且不能有一个d[i]>r,然后其他跟普通线段树一样了。
具体看码:
#include<cstdio>
#include<iostream>
#define ll long long
#define ls num<<1
#define rs num<<1|1
#define lson num<<1,l,mid
#define rson num<<1|1,mid+1,r
using namespace std;
ll lazy[3000007],ans;
struct tree{
ll min,max,w;
}tr[3000007];
void pushdown(int num,int l,int r){//下传
if(lazy[num]){//lazy记录的是此区间的修改最大值
//由于我们知道当区间所有值都不大于r时我们才直
//接统计答案并且懒标记,所以它的儿子的最大值最小值也是
//直接被lazy更新的,或者来说,此区间,此区间的儿子的值全被替换成lazy
int mid=l+r>>1;
lazy[ls]=max(lazy[num],lazy[ls]);
tr[ls].min=tr[ls].max=lazy[num];//lazy必然比儿子区间任何数大
tr[ls].w=(ll)(mid-l+1)*lazy[num];//总和必然是区间个数*lazy
lazy[rs]=max(lazy[num],lazy[rs]);
tr[rs].min=tr[rs].max=lazy[num];
tr[rs].w=(ll)(r-mid)*lazy[num];
lazy[num]=0;
}
}
void build(int num,int l,int r){//建树没什么变化
if(l==r){tr[num].w=tr[num].min=tr[num].max=l;}//
else{
int mid=l+r>>1;
build(lson);build(rson);
tr[num].w=tr[ls].w+tr[rs].w;
tr[num].min=min(tr[ls].min,tr[rs].min);
tr[num].max=max(tr[ls].max,tr[rs].max);
}
}
void query(int num,int l,int r,ll x,ll y){
pushdown(num,l,r);//下传先
if(l>=x&&r<=y&&tr[num].max<=y){ans+=(ll)(r-l+1)*y-tr[num].w;}
//看到了吗?最大的不能超过y
else{
int mid=l+r>>1;
if(x>l||y<r){//此区间并不被x,y完全包含
if(x<=mid) query(lson,x,y);
if(y>mid) query(rson,x,y);//向儿子下传
}else{//到了这说明此区间被完全包含,但是最大值超过了y
if(tr[rs].min<=y){//当右儿子最小值<=y时,说明右区间有着被统计答案,同理由于d数组是单调不递减的,所以此时左儿子可以直接统计答案了
ans+=(ll)(mid-l+1)*y-tr[ls].w;
query(rson,x,y);
}else if(tr[ls].min<=y)//右区间没用了,判断左区间有没有用(但是感觉好像没必要吧?)
query(lson,x,y);
}
}
}
void change(int num,int l,int r,ll x,ll y){//下传或者更新的操作不变,就是统计答案换成了取最大值。
pushdown(num,l,r);
if(l>=x&&r<=y&&tr[num].max<=y){
lazy[num]=max(lazy[num],y);
tr[num].min=tr[num].max=y;
tr[num].w=(ll)(r-l+1)*y;
}else{
int mid=l+r>>1;
if(x>l||y<r){
if(x<=mid) change(lson,x,y);
if(y>mid) change(rson,x,y);
}else{
if(tr[rs].min<=y)
change(rson,x,y),change(lson,x,y);
else if(tr[ls].min<=y)
change(lson,x,y);
}
tr[num].w=tr[ls].w+tr[rs].w;
tr[num].min=min(tr[ls].min,tr[rs].min);
tr[num].max=max(tr[ls].max,tr[rs].max);
}
}
int main(){
freopen("ohmygod.in","r",stdin);
freopen("ohmygod.out","w",stdout);
int n,m;
scanf("%d%d",&n,&m);
build(1,1,n);//建树
for(int i=1;i<=m;i++){
ll l,r;
scanf("%lld%lld",&l,&r);
ans=0;
query(1,1,n,l,r);//查询答案
change(1,1,n,l,r);//将l到r之间的d[i]=max(d[i],r)
printf("%lld\n",ans);
}
}
跑了890ms,吉司机的话应该是100多。
好了最近比较有意思的就这几题了,今天比赛也有一题,但是已经10点了,所以明天再打把。