这场训练的时候就过了四题,赛后看了题解感觉还是有很多可做题的。
题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6364
题解
首先新郎和新娘的配对肯定不会有反向交叉,即3和1配对,2和4配对这样的情况。所以我们可以枚举断点然后计算答案。
统计答案显然就按顺时针的顺序贪心连线就好了。
当时想到这里然后就不会做了,并不知道改变断点之后怎么处理。
我们可以把新郎看成左括号,新娘看成有括号,从左往后左括号+1右括号-1。每个人的贡献分为两类,前缀和>0的新郎贡献为负,<=0的新郎贡献为正。前缀和>=0的新娘贡献为正,<0的新娘贡献为负。
每次移动一个位置等价于所有人的前缀和+1或-1,只会影响两种前缀和的贡献的正负性。
所以我们对于每种前缀和维护一下有多少个以及其正负性,然后每次右移更新一下就好了。
代码
#include
#define ll long long
#define N 500010
#define M 2000010
using namespace std;
int T,n,L,a[N],b[N],tp[M],s[M],f[M],v[M],inv1[M],inv2[M];
ll res,A[M],B[M],ans;int sum,tot,val;
char ss[N],*h=ss+N,*t=h;
inline char getch()
{
if(h==t)
{
if(t!=ss+N)return EOF;
t=ss+fread(ss,1,N,stdin);
h=ss;
}
return *h++;
}//卡读入丧心病狂
inline int get()
{
char ch;int v;
while(!isdigit(ch=getch()));v=ch-48;
while(isdigit(ch=getch()))v=v*10+ch-48;
return v;
}
int main()
{
T=get();
while(T--)
{
n=get();L=get();res=(ll)n*L;sum=0;ans=0;
for(int i=1;i<=n;i++)a[i]=get();
for(int i=1;i<=n;i++)b[i]=get();
for(int i=0;i<=n*2;i++)A[i]=B[i]=0,inv1[i]=inv2[i]=1;
a[n+1]=L+1;b[n+1]=L+1;tot=0;
for(int i=1,j=1;i<=n||j<=n;)
{
if(a[i]s[++tot]=a[i++],tp[tot]=1;
else s[++tot]=b[j++],tp[tot]=-1;
}
for(int i=1;i<=tot;i++)s[tot+i]=L+s[i],tp[tot+i]=tp[i];
for(int i=1;i<=tot;i++)
{
sum+=tp[i];f[i]=sum;
if(tp[i]>0)v[i]=(sum>0?-1:1)*s[i];
else v[i]=(sum<0?-1:1)*s[i];
ans+=v[i];
tp[i]>0?A[n+sum]+=v[i]:B[n+sum]+=v[i];
}
for(int i=1,j=tot+1,pos=0;i<=tot;i++,j++)
{
res=min(res,ans);
if(tp[i]>0)A[n+f[i]]-=inv1[n+f[i]]*v[i],ans-=inv1[n+f[i]]*v[i];
else B[n+f[i]]-=inv2[n+f[i]]*v[i],ans-=inv2[n+f[i]]*v[i];
if(tp[i]>0)
{
ans-=A[n+pos+1]*2; ans-=B[n+pos]*2;
A[n+pos+1]=-A[n+pos+1]; B[n+pos]=-B[n+pos];
inv1[n+pos+1]=-inv1[n+pos+1]; inv2[n+pos]=-inv2[n+pos];
}
else
{
ans-=A[n+pos]*2;ans-=B[n+pos-1]*2;
A[n+pos]=-A[n+pos];B[n+pos-1]=-B[n+pos-1];
inv1[n+pos]=-inv1[n+pos]; inv2[n+pos-1]=-inv2[n+pos-1];
}
pos+=tp[i];sum+=tp[j]-tp[i];
val=s[j];ans+=val;
tp[j]>0?A[n+sum+pos]+=val:B[n+sum+pos]+=val;
}
printf("%I64d\n",res);
}
return 0;
}
题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6365
题解
其实就是一个简单的区间dp。
将点极角排序,对于每个区间,只需要考虑区间中被完全覆盖的点就好了,对于剩下的点完全可以等到更大的区间再去考虑。当时一直没想通。。。
一个结论是,每次射击一定是射在2n个线段端点中的某一个。
对于一个区间,要想将该区间完全覆盖的线段都射光,就必须有一次射击要射到权值最大的那个线段。
所以预处理出每个区间完全覆盖的最大的线段,枚举在那条线段上射击的点,将另外两半的区间进行区间dp合并就好了。
代码
#include
#define ll long long
#define inf 999999999999999ll
#define N 605
using namespace std;
int T,n,tot,l[N],r[N],h[N],w[N],L[N],R[N];
ll f[N][N];
struct node{
ll x,y;
bool operator<(const node &p)const{
return x*p.y-y*p.x<0;
}
bool operator==(const node &p)const{
return x==p.x&&y==p.y;
}
}t[N];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);tot=0;
memset(L,0,sizeof(L));
memset(R,0,sizeof(R));
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&h[i],&l[i],&r[i],&w[i]);
t[++tot]=(node){l[i],h[i]};
t[++tot]=(node){r[i],h[i]};
}
sort(t+1,t+tot+1);
tot=unique(t+1,t+tot+1)-t-1;
for(int i=1;i<=n;i++)
{
L[i]=lower_bound(t+1,t+tot+1,(node){l[i],h[i]})-t;
R[i]=lower_bound(t+1,t+tot+1,(node){r[i],h[i]})-t;
}
for(int len=1;len<=tot;len++)
for(int i=1;i+len-1<=tot;i++)
{
int j=i+len-1,maxn=0,x,y;f[i][j]=inf;
for(int k=1;k<=n;k++)
if(i<=L[k]&&R[k]<=j&&w[k]>maxn)
x=L[k],y=R[k],maxn=w[k];
if(!maxn){f[i][j]=0;continue;}
for(int k=x;k<=y;k++)
f[i][j]=min(f[i][j],f[i][k-1]+maxn+f[k+1][j]);
}
printf("%I64d\n",f[1][tot]);
}
return 0;
}
题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6368
题解
最小方差生成树。
以前写过一道最小极差生成树,贴了发代码然后T了(雾)。
后来想想好像不太对。
首先最小方差生成树肯定是一段连续的边构成的最小生成树。
一种暴力做法是枚举平均数,根据平均数确定边权然后上mst,显然T飞,不过可以给我们思路。
对于两条冲突的边a和b,假设w[a]
#include
#define ll long long
#define ld long double
#define N 400010
#define inf 2100000000
#define iinf 9999999999999999.0
#define mod 998244353
using namespace std;
int Tx,n,m,tot,fa[N],L[N],R[N],pa[N];
ll sum1,sum2,ans;ld res;
struct node{int c[2],fa,rev,minn,num,pos;}t[N];
struct edge{
int a,b,c;
bool operator<(const edge &p)const{return cint x,y,inv;
bool operator<(const info &p)const{return yy;}
}s[N];
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=(ll)res*a%mod;
a=(ll)a*a%mod;b>>=1;
}
return res;
}
class lct
{
void pushdown(int x)
{
if(!t[x].rev)return;
int lc=t[x].c[0],rc=t[x].c[1];
if(lc)t[lc].rev^=1,swap(t[lc].c[0],t[lc].c[1]);
if(rc)t[rc].rev^=1,swap(t[rc].c[0],t[rc].c[1]);
t[x].rev=0;
}
void update(int x)
{
int lc=t[x].c[0],rc=t[x].c[1];
t[x].minn=t[x].num;t[x].pos=x;
if(lc&&t[lc].minnx].minn)
t[x].minn=t[lc].minn,t[x].pos=t[lc].pos;
if(rc&&t[rc].minnx].minn)
t[x].minn=t[rc].minn,t[x].pos=t[rc].pos;
}
void rotate(int x,int k)
{
int y=t[x].fa,f=(t[t[y].fa].c[1]==y);
pushdown(y);pushdown(x);
if(!t[y].fa)fa[x]=fa[y];
t[x].fa=t[y].fa;t[t[y].fa].c[f]=x;
t[y].fa=x;t[y].c[k]=t[x].c[k^1];
t[t[y].c[k]].fa=y;t[x].c[k^1]=y;
update(y);
}
void splay(int x)
{
while(t[x].fa)
{
int y=t[x].fa,f=t[y].c[1]==x;
if(!t[y].fa)rotate(x,f);
else
{
if(f==(t[t[y].fa].c[1]==y))rotate(y,f),rotate(x,f);
else rotate(x,f),rotate(x,f^1);
}
}
pushdown(x);update(x);
}
void access(int x)
{
for(int y=0;x;y=x,x=fa[x])
{
splay(x);
t[t[x].c[1]].fa=0;fa[t[x].c[1]]=x;
t[x].c[1]=y;t[y].fa=x;fa[y]=0;
update(x);
}
}
int lca(int x,int y)
{
access(x);
for(splay(y);fa[y];splay(y))y=fa[y];
return y;
}
public:
void link(int x,int y)
{
access(x);splay(x);fa[x]=y;
t[x].rev^=1;swap(t[x].c[0],t[x].c[1]);
}
void cut(int x,int y)
{
access(x);splay(y);
if(fa[y]==x)swap(x,y);splay(x);
t[t[x].c[0]].fa=0;fa[t[x].c[0]]=0;
t[x].c[0]=fa[x]=0;update(x);
}
int qry(int x,int y)
{
int p=lca(x,y),pos,res=p;
access(x);splay(p);pos=t[p].c[1];
if(pos&&t[pos].minnpos].pos;
access(y);splay(p);pos=t[p].c[1];
if(pos&&t[pos].minnpos].pos;
return res;
}
}T;
int find(int x)
{
if(pa[x]==x)return x;
return pa[x]=find(pa[x]);
}
int main()
{
int a,b,c,x,y,p;
scanf("%d",&Tx);
while(Tx--)
{
scanf("%d%d",&n,&m);res=iinf;tot=0;
for(int i=1;i<=m;i++)R[i]=inf;
for(int i=1;i<=n;i++)pa[i]=i;
for(int i=1;i<=n+m;i++)
{
t[i].c[0]=t[i].c[1]=t[i].rev=0;
t[i].pos=i;t[i].fa=fa[i]=0;
if(i<=n)t[i].minn=t[i].num=m+1;
else t[i].minn=t[i].num=i-n;
}
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
sort(e+1,e+m+1);
for(int i=1;i<=m;i++)
{
a=e[i].a;b=e[i].b;c=e[i].c;
x=find(a);y=find(b);
if(x!=y)L[i]=-inf,pa[y]=x;
else
{
p=T.qry(a,b)-n;R[p]=L[i]=e[p].c+c;
T.cut(e[p].a,p+n);T.cut(e[p].b,p+n);
}
T.link(a,i+n);T.link(b,i+n);
}
for(int i=1;i<=m;i++)
{
s[++tot]=(info){e[i].c,L[i],1};
s[++tot]=(info){e[i].c,R[i],-1};
}
sort(s+1,s+tot+1);sum1=0;sum2=0;
for(int i=1,cnt=0;i<=tot;)
{
int pos=s[i].y;
for(;s[i].y==pos;i++)
{
sum1+=s[i].inv*s[i].x;cnt+=s[i].inv;
sum2+=(ll)s[i].inv*s[i].x*s[i].x;
}
if(cnt!=n-1)continue;
ld tmp=(ld)sum1*sum1/cnt+sum2-(ld)2*sum1*sum1/cnt;
if(tmp>=res)continue;res=tmp;
ll num=Pow(cnt,mod-2),A=sum1%mod*num%mod;
ans=(A*A%mod*cnt%mod+sum2%mod-2*A*sum1%mod)*num%mod;
}
printf("%d\n",(ans+mod)%mod);
}
return 0;
}
题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6371
题解
n之后21,一看就是一道爆搜题。。。
先写了一发2^n的大暴搜加上O(nlogn)求中位数,然后T飞。
把中位数改成O(n)求,然后T飞。
改成一遍搜一遍拿棵线段树维护中位数,然后T飞。
把搜索的顺序换一下,先搜卡牌多的卡组后搜卡牌少的卡组,然后T飞。
跑去看题解了,然后发现可以倒着搜,拿个链表维护中位数。
每次链表删除一个数很简单,回溯的时候其实就是撤销之前的删除操作,也很简单。
然后就过了。
代码
#include
#define inf 2147483647
#define N 205
using namespace std;
int Tx,n,m,ans,sum,mid,s[N],A;
int tot,Tnum,num[N],po[N],cnt[N];
int len[N],t[N][N],pos[N],v[N][N],pre[N],nxt[N];
inline bool cmp(const int &a,const int &b){return len[a]>len[b];}
void link(int x)
{
nxt[pre[x]]=x;
pre[nxt[x]]=x;tot++;
if(tot&1){if(xelse if(x>A)A=nxt[A];
}
void cut(int x)
{
if(tot&1){if(x<=A)A=nxt[A];}
else if(x>=A)A=pre[A];
nxt[pre[x]]=nxt[x];
pre[nxt[x]]=pre[x];tot--;
}
void dfs(int y,int sum)
{
if(y>n)
{
if(!tot)return;
if(tot&1)ans=max(ans,num[A]*2-sum);
else ans=max(ans,num[A]+num[pre[A]]-sum);
return;
}
int x=po[y];dfs(y+1,sum);
for(int i=0,p;ix];++i)
p=t[x][i],cut(v[p][--pos[p]]);
dfs(y+1,sum-s[x]);
for(int i=len[x]-1,p;i>=0;--i)
p=t[x][i],link(v[p][pos[p]++]);
}
int main()
{
scanf("%d",&Tx);
while(Tx--)
{
scanf("%d%d",&n,&m);ans=-inf;tot=0;sum=0;
for(int i=1;i<=n;++i)scanf("%d",&s[i]),sum+=s[i];
for(int i=1;i<=n;++i)
{
scanf("%d",&len[i]);po[i]=i;
for(int j=0;j"%d",&t[i][j]);
}
sort(po+1,po+n+1,cmp);
for(int i=1;i<=m;++i)
{
scanf("%d",&pos[i]);
for(int j=0;j<pos[i];++j)
scanf("%d",&v[i][j]),num[++tot]=v[i][j];
}
sort(num+1,num+tot+1);
for(int i=1;i<=tot;i++)cnt[i]=0;
for(int i=1;i<=m;i++)
for(int j=0;j<pos[i];j++)
cnt[v[i][j]=lower_bound(num+1,num+tot+1,v[i][j])-num]++;
for(int i=1;i<=tot;i++)cnt[i]+=cnt[i-1];
for(int i=1;i<=m;i++)
for(int j=0;j<pos[i];j++)v[i][j]=cnt[v[i][j]]--;
for(int i=1;i<=tot;i++)pre[i]=i-1,nxt[i]=i+1;
pre[tot+1]=tot;pre[0]=1;
A=tot/2+1;dfs(1,sum);
printf("%d\n",ans);
}
return 0;
}
题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6372
题解
根据lucas定理,c[i][j]modp 等价于把i和j搞成p进制求个组合数再乘起来。
c[i][j] mod p!=0 的条件是,将i和j拆解成p进制后,每一对组合数都不为0。
也就是说,对于每一块c(a,b),必须保证a>=b。
所以对于矩阵中的每一个点,HMBB[i][j]=1的条件是i和j分解成p进制之后每一位i都不小于j。我们把这样的i和j连边。
这样的话,f[i][j] 就表示j个i位p进制数,满足每个数的每一位不小于上一个数的方案数。
所以这就变成了一个组合计数问题。
显然每一位独立,f[i][j]=C(j+p,p-1)^i。
ans=ΣΣf[i][j],上一发等比数列求和就好了。
代码
#include
#define ll long long
#define mod 1000000007
#define N 2000010
using namespace std;
int T,c,n,k,tot,ans,prime[N],check[N],fac[N],inv[N];
const int maxn=2000000;
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=(ll)res*a%mod;
a=(ll)a*a%mod;b>>=1;
}
return res;
}
int C(int a,int b){return (ll)fac[a]*inv[b]%mod*inv[a-b]%mod;}
int main()
{
int tmp,res;
for(int i=2;i<=maxn;i++)
{
if(!check[i])prime[++tot]=i;
for(int j=1;j<=tot;j++)
{
if(i*prime[j]>maxn)break;
check[i*prime[j]]=1;
if(!(i%prime[j]))break;
}
}
fac[0]=1;
for(int i=1;i<=maxn;i++)fac[i]=(ll)fac[i-1]*i%mod;
inv[maxn]=Pow(fac[maxn],mod-2);
for(int i=maxn;i;i--)inv[i-1]=(ll)inv[i]*i%mod;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&c,&n,&k);ans=0;
for(int i=1;i<=k;i++)
{
tmp=C(i+prime[c],prime[c]-1);
if(tmp==1)res=n;
else res=(ll)(Pow(tmp,n)-1)*Pow(tmp-1,mod-2)%mod*tmp%mod;
ans=(ans+res)%mod;
}
printf("%d\n",(ans+mod)%mod);
}
return 0;
}