Gym101002 2016NAIPC(队内第7次训练)
(由于先看的最后一题,然后又一直WA,导致这场有点爆炸,我背锅。
A .Fancy Antiques
题意: 选择最多k个商店,买n个物品,每个物品分别对应两个店售卖,求最小花费是多少。n<100,k=m<=40;
思路:搜索。。。。开始以为是个费用流,然后没法限制。加N多减枝,然后....
B. 好像没什么可以学的,懒得看了
C .Greetings!
pro:N种卡片,用K种格子去装。问浪费的空间。N,K<15
sol:比较小,状态压缩。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; int n,k,w[20],h[20],q[20]; long long f[1<<16][20]; int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)scanf("%d%d%d",w+i,h+i,q+i); memset(f,127,sizeof(f)); for(int i=0;i<=k;i++)f[0][i]=0; for(int i=1;i<(1<<n);i++) for(int p=i;p;p=(p-1)&i){ int maxw=0,maxh=0;long long now=0; for(int j=1;j<=n;j++) if(1<<(j-1)&p)maxw=max(maxw,w[j]),maxh=max(maxh,h[j]); for(int j=1;j<=n;j++) if(1<<(j-1)&p)now+=(long long)(maxw*maxh-w[j]*h[j])*q[j]; for(int j=1;j<=k;j++) f[i][j]=min(f[i][j],f[i^p][j-1]+now); } printf("%lld",f[(1<<n)-1][k]); return 0; }
D .Programming Team
题意:给定一棵大小为N的点权树(si,pi),现在让你选敲好K个点,需要满足如果如果u被选了,那么fa[u]一定被选,现在要求他们的平均值(pi之和/si之和)最大。
思路:均值最大,显然需要01分数规划,但是后面怎么高效的做背包,我是不会的,我只会暴力的背包,O(N*K*K)。 还好队友会,叫“树上依赖背包”,即要问树转化为DFS序,按照倒序来DP,复杂度O(N*K)。dp[i][j]=max(dp[i+1][j-v[x]]+w[x],dp[i+sz[x]][j]),分别对应x选或者不选(其中x是DFS序为i的点)。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2610; const double inf=1e20; double dp[maxn][maxn],s[maxn],p[maxn],val[maxn]; int r[maxn],Laxt[maxn],Next[maxn],To[maxn]; int pos[maxn],tot,cnt,N,K,sz[maxn]; void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } void dfs(int u) { sz[u]=1; tot++; pos[tot]=u; for(int i=Laxt[u];i;i=Next[i]){ dfs(To[i]); sz[u]+=sz[To[i]]; } } bool check(double Mid) { rep(i,1,N) val[i]=p[i]-s[i]*Mid; rep(i,1,tot+1) rep(j,1,K) dp[i][j]=-inf; rep(i,0,tot+1) dp[i][0]=0; for(int i=tot;i>=1;i--){ int x=pos[i]; for(int j=1;j<=K;j++){ dp[i][j]=max(dp[i+1][j-1]+val[x],dp[i+sz[x]][j]); } } return dp[1][K]>=0; } int main() { scanf("%d%d",&K,&N); K++; rep(i,1,N){ scanf("%lf%lf%d",&s[i],&p[i],&r[i]); add(r[i],i); } dfs(0); double L=0,R=10000,Mid,ans=0; int T=30; while(T--){ Mid=(L+R)/2; if(check(Mid)) ans=max(Mid,ans),L=Mid; else R=Mid; } printf("%.3lf\n",ans); return 0; }
E. K-Inversions
pro:对(A,B)的长度贡献。
sol:FFT模板题。
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> struct cp{ double x,y; cp operator +(const cp&a)const{ return (cp){x+a.x,y+a.y}; } cp operator -(const cp&a)const{ return (cp){x-a.x,y-a.y}; } cp operator *(const cp&a)const{ return (cp){x*a.x-y*a.y,x*a.y+y*a.x}; } }a[5000000],b[5000000],c[5000000]; int n,m,len,ans[5000000];char s[1000010]; double pi=3.14159265358979323846; void DFT(cp *a,int len,int type){ for(int i=0,t=0;i<len;i++){ if(i<t)std::swap(a[i],a[t]); for(int j=len>>1;(t^=j)<j;j>>=1); } for(int h=2;h<=len;h<<=1){ cp wn=(cp){cos(pi*2*type/h),sin(pi*2*type/h)}; for(int i=0;i<len;i+=h){ cp w=(cp){1,0},t; for(int j=0;j<h>>1;j++,w=w*wn){ t=a[i+j+(h>>1)]*w; a[i+j+(h>>1)]=a[i+j]-t; a[i+j]=a[i+j]+t; } } } } int main(){ scanf("%s",s);n=strlen(s); for(int i=0;i<n;i++) if(s[i]=='A')a[i].x=1; else b[n-i-1].x=1; for(len=1;len<=n+n;len<<=1); DFT(a,len,1),DFT(b,len,1); for(int i=0;i<len;i++)c[i]=a[i]*b[i]; DFT(c,len,-1); for(int i=0;i<len;i++)ans[i]=c[i].x/len+0.1; for(int i=n;i<n+n-1;i++)printf("%d\n",ans[i]); return 0; }
G .Symmetry
pro:现在给你二维平面上N个点,现在让你求,加最少的点,使得所有点关于同一个点或者线有对称点。N<1e3;
sol:好像是枚举的,枚举对称中心,枚举对称轴,复杂度O(N^3);
H .Jewel Thief
pro:给定N,M。输入N个物品,(si,vi)表示第i个物品体积为si,价值为vi,s<=300,vi<=1e9; N<1e6;现在要求,对于背包体积为1到M时,求出最大背包价值。
sol:显然直接跑背包会爆炸。 发现物品体积都比较小,我们先对相同体积的排序,对于体积相同的一起处理。
然后发现转移都是在差为体积整数倍之间,按照容量对体积取模分组 ,发现组内部转移满足神奇的决策单调性。 然后就是s次分治。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) #define rep2(i,a,b) for(int i=b;i>=a;i--) using namespace std; const int maxn=310; const int maxm=100010; ll dp[2][maxm]; vector<ll>G[maxn]; int x,d,t; void get(int L,int R,int l,int r) { if(L>R) return ; int Mid=(L+R)>>1,pos=Mid; for(int i=min(r,Mid-1);i>=l;i--){ if(Mid-i>G[x].size()) break; if(dp[t][d+Mid*x]<dp[t^1][d+i*x]+G[x][Mid-i-1]){ pos=i; dp[t][d+Mid*x]=dp[t^1][d+i*x]+G[x][Mid-i-1]; } } get(L,Mid-1,l,pos); get(Mid+1,R,pos,r); } int main() { int N,M,s;ll v; scanf("%d%d",&N,&M); rep(i,1,N){ scanf("%d%lld",&s,&v); G[s].push_back(v); } rep(i,1,300){ if(G[i].size()==0) continue; sort(G[i].begin(),G[i].end(),greater<int>() ); for(int j=1;j<G[i].size();j++) G[i][j]+=G[i][j-1]; t^=1; rep(j,1,M) dp[t][j]=dp[t^1][j]; rep(j,0,i-1){ d=j; x=i; get(0,(M-j)/i,0,(M-j)/i); } rep(j,1,M) dp[t][j]=max(dp[t][j],dp[t][j-1]); } rep(i,1,M) printf("%lld ",dp[t][i]); return 0; }
I. Tourists
pro:给定一棵树,求所有倍数关系的点之间距离之和。
sol:枚举所有的,倍增求LCA,O(N*logN*logN),也可以用ST表O(NlogN);
#include<bits/stdc++.h> #define pii pair<int,int> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; struct edge{ int d,nex; }e[400010]; int n,q[200010],l,efree,d[200010],f[20][200010]; long long ans; inline void add(int x,int y){e[++efree]=(edge){y,q[x]};q[x]=efree;} void dfs(int x,int y){ f[0][x]=y;d[x]=d[y]+1; for(int i=q[x];i;i=e[i].nex) if(e[i].d!=y)dfs(e[i].d,x); } inline int lca(int x,int y){ if(d[x]>d[y])swap(x,y); for(int i=log2(d[y]-d[x]);i>=0;i--) if(d[f[i][y]]>=d[x])y=f[i][y]; if(x==y)return x; for(int i=log2(d[x]);i>=0;i--) if(f[i][x]!=f[i][y])x=f[i][x],y=f[i][y]; return f[0][x]; } int main(){ scanf("%d",&n); for(int i=1;i<n;i++){ int x,y;scanf("%d%d",&x,&y); add(x,y),add(y,x); } dfs(1,0);l=log2(n); for(int i=1;i<=l;i++) for(int j=1;j<=n;j++)f[i][j]=f[i-1][f[i-1][j]]; for(int i=1;i<=n>>1;i++) for(int j=i+i;j<=n;j+=i){ int fa=lca(i,j); ans+=d[i]+d[j]-2*d[fa]+1; } printf("%lld",ans); return 0; }
F. Mountain Scenes
pro:给定总长度为N的木棍,一个W*H的框框,问有多少种分放方式,使得图案不是平的。N<10000;W,H<100
sol:DP即可,用总的分放方案减去平的。
#include<bits/stdc++.h> #define pii pair<int,int> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; ll dp[101][10001]; const int MOD = 1e9 + 7; int main() { int n, w, h; scanf("%d%d%d", &n, &w, &h); if(n > w * h)n = w * h; for(int i = 0; i <= w; i++)dp[i][0] = 1; for(int i = 1; i <= w; i++) for(int j = 1; j <= n && j <= i * h; j++) for(int k = 0; k <= h && k <= j; k++)dp[i][j] += dp[i - 1][j - k], dp[i][j] %= MOD; ll ans = 0; for(int i = 1; i <= n; i++)ans += dp[w][i], ans %= MOD; ans -= (n / w); printf("%lld\n", ans); return 0; }
J .Whiteboard
pro:给定N*M的画板,然后给定画板的最终状态,以及画笔的走向。现在在某个时间T,画笔变为了橡皮擦,即最开始画板的白色的,T之前笔走过是染黑,T之后笔走过的漂白,求T的范围[L,R],不存在则输出-1 -1。
sol:首先,如果画笔没有走过的地方是黑色'#',则无解。
对于画笔走过的地方,我们关系的其实只有最后一次走过的时间ti,对于黑色,L>=ti; 对于白色,R<ti;
这种题,我们倒着去删点就可以了,删点可以利用并查集,链表,或者直接用set的二分功能即可。
注意使用long long。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=1000010; char c[maxn],s[maxn],q[maxn][8]; set<int>RR[maxn],CC[maxn]; set<int>::iterator it; int d[maxn],X[maxn],Y[maxn],N,M,Q; ll times[maxn],Mx[maxn]; void solve(int nowx,int nowy,int tox,int toy,int x,int y,ll nowt) { if(abs(x)==1){ int L=min(nowx,tox),R=max(nowx,tox); for(it=CC[nowy].lower_bound(L);;it=CC[nowy].lower_bound(L)){ if(it==CC[nowy].end()||(*it)>R) break; L=*it;CC[nowy].erase(it); RR[L].erase(RR[L].find(nowy)); Mx[(L-1)*M+nowy]=nowt+abs(L-nowx); } } else { int L=min(nowy,toy),R=max(nowy,toy); for(it=RR[nowx].lower_bound(L);;it=RR[nowx].lower_bound(L)){ if(it==RR[nowx].end()||(*it)>R) break; L=*it;RR[nowx].erase(it); CC[L].erase(CC[L].find(nowx)); Mx[(nowx-1)*M+L]=nowt+abs(L-nowy); } } } int main() { scanf("%d%d%d",&N,&M,&Q); for(int i=N;i>=1;i--) { scanf("%s",c+1); rep(j,1,M) s[(i-1)*M+j]=c[j]; } rep(i,1,Q) scanf("%s%d",q[i],&d[i]); rep(i,1,N) rep(j,1,M) RR[i].insert(j),CC[j].insert(i); X[0]=1; Y[0]=1; times[0]=1; rep(i,1,Q){ if(q[i][0]=='u') X[i]=X[i-1]+d[i],Y[i]=Y[i-1]; else if(q[i][0]=='d') X[i]=X[i-1]-d[i],Y[i]=Y[i-1]; else if(q[i][0]=='r') X[i]=X[i-1],Y[i]=Y[i-1]+d[i]; else X[i]=X[i-1],Y[i]=Y[i-1]-d[i]; times[i]=times[i-1]+d[i]; } for(int i=Q;i>=1;i--){ if(q[i][0]=='u') solve(X[i-1],Y[i-1],X[i],Y[i],1,0,times[i-1]); else if(q[i][0]=='d') solve(X[i-1],Y[i-1],X[i],Y[i],-1,0,times[i-1]); else if(q[i][0]=='l') solve(X[i-1],Y[i-1],X[i],Y[i],0,-1,times[i-1]); else solve(X[i-1],Y[i-1],X[i],Y[i],0,1,times[i-1]); } ll L=0,R=times[Q]; rep(i,1,N*M){ if(!Mx[i]&&s[i]=='#') R=-1; if(!Mx[i]) continue; if(s[i]=='#') L=max(L,Mx[i]); else R=min(R,Mx[i]-1); } if(L<=R) printf("%lld %lld\n",L,R); else puts("-1 -1"); return 0; }
K .YATP
题意:给定带点权边权的树,定义路径的花费=路径边权和e+起点点权w[s]*终点点权w[t]。N<2e5,e,w<1e6;
思路:首先,需要树分治。 然后得到方程dp[i]=min{ dis[i]+dis[j]+w[i]*w[j] },很显然需要斜率优化。
注意维护凸包的时候是需要保证w[j]是单调的,这样才能用不等式维护队尾。 由于w[i]不是对应的队尾,所以我们还要二分凸包。
还有个问题,怎么确定我们得到的i和j不是在同一个子树呢? 因为如果在一颗子树的时候dp[i]=dis[i]+dis[j]+w[i]*w[j]-2*dis[LCA]。 其实没必要考虑这个问题,因为当LCA为根的时候会更新答案。(这一点想不到估计要很难去维护了)
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=400010; int Laxt[maxn],Next[maxn],To[maxn],Len[maxn],cnt; int sz[maxn],son[maxn],rt,all,vis[maxn],S[maxn],tot; ll ans[maxn],a[maxn],dis[maxn],sum; int q[maxn],top; bool cmp(int x,int y) { int xx=x,yy=y; if(a[xx]==a[yy]) return dis[xx]<dis[yy]; return a[xx]<a[yy]; } ll getans(int p,int k) { return dis[k]+dis[p]+a[p]*a[k]; } void add(int u,int v,int w) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; Len[cnt]=w; } void dfs1(int u,int f) { sz[u]=1; son[u]=0; for(int i=Laxt[u];i;i=Next[i]){ if(To[i]!=f&&!vis[To[i]]) { dfs1(To[i],u); sz[u]+=sz[To[i]]; son[u]=max(son[u],sz[To[i]]); } } son[u]=max(son[u],all-son[u]); if(son[u]<son[rt]) rt=u; } void cal(int p) { if(top==0) return ;int L=1,R=top-1,Mid; ans[p]=min(ans[p],getans(p,q[top])); while(L<=R){ Mid=(L+R)>>1; ll tmp1=getans(p,q[Mid]),tmp2=getans(p,q[Mid+1]); if(tmp1<tmp2) R=Mid-1,ans[p]=min(ans[p],tmp1); else L=Mid+1,ans[p]=min(ans[p],tmp2); } } void get(int u,int f) { cal(u); for(int i=Laxt[u];i;i=Next[i]) if(To[i]!=f&&!vis[To[i]]) get(To[i],u); } bool check(int p){ return (dis[p]-dis[q[top]])*(a[p]-a[q[top-1]])<= (dis[p]-dis[q[top-1]])*(a[p]-a[q[top]]); } void ADD(int p) { if(top&&a[p]==a[q[top]]&&dis[p]<dis[q[top]]) top--; while(top>1&&check(p)) top--; q[++top]=p; } void get(int u,int f,ll D) { dis[u]=D; sz[u]=1; S[++tot]=u; for(int i=Laxt[u];i;i=Next[i]) if(To[i]!=f&&!vis[To[i]]){ get(To[i],u,D+Len[i]); sz[u]+=sz[To[i]]; } } void solve(int u,int f) { vis[u]=1; dis[u]=0; top=0; tot=0; S[++tot]=u; for(int i=Laxt[u];i;i=Next[i]){ if(!vis[To[i]]&&To[i]!=f) get(To[i],u,Len[i]); } sort(S+1,S+tot+1,cmp); rep(i,1,tot) ADD(S[i]); rep(i,1,tot) cal(S[i]); for(int i=Laxt[u];i;i=Next[i]){ int v=To[i]; if(!vis[v]&&v!=f) { all=sz[v]; rt=0; dfs1(v,0); solve(rt,0); } } } int main() { int N,u,v,w; scanf("%d",&N); son[0]=N+1; rep(i,1,N) scanf("%lld",&a[i]); rep(i,1,N) ans[i]=a[i]*a[i]; rep(i,1,N-1){ scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } all=N; rt=0; dfs1(1,0); solve(rt,0); rep(i,1,N) sum+=ans[i]; printf("%lld\n",sum); return 0; }