感觉是一项大工程。。不知道能打多少。
玩具谜题
直接模拟就行。
#include<bits/stdc++.h>
using namespace std;
const int N = 100000 + 10;
const int M = 10 + 2;
int n,m;
int a[N];
char s[N][M];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);gets(s[i]);
}
int tail=1;
while(m--){
int x,s;scanf("%d%d",&x,&s);
if(a[tail]==0&&x==0) tail-=s,tail=(tail%n+n)%n;
else if(a[tail]==0&&x==1) tail+=s,tail%=n;
else if(a[tail]==1&&x==0) tail+=s,tail%=n;
else tail-=s,tail=(tail%n+n)%n;
if(tail==0) tail=n;
}
for(int i=1;i<strlen(s[tail]);++i) printf("%c",s[tail][i]);
printf("\n");
return 0;
}
天天爱跑步。
虽然正解比暴力短。。。考场上还是写暴力吧。
路径拆分。
1.如果s在u的子树中,且dep[u]+w[u]=dep[s],则以s为起点的这条路径对u有1的贡献。
2.如果t在u的子树中,且dep[t]-dis(s,t)=dep[u]-w[u],则以t为终点的这条路径对u有1的贡献。
3.注意如果u是s和t的lca,则u会被统计到两次,要注意减去。
所以其实这个可以直接用桶up[x],记录深度为x的s有多少个,down[x]记录dep[t]-dis(s,t)为x的t有多少个,所以每次便利到u的时候,就直接加上up[dep[u]+w[u]]和dep[dep[u]-w[u]].
1.不过还有一些细节:dep[t]-dis(s,t)可能为负,而数组下标不能为负,所以+n
2为了满足统计到的路径都是经过u这个节点的路径,所以我们在离开u的子树的时候,要把以u为lca的贡献都减掉。
3.在进u这棵子树的时候先记录一个前驱,表示进入u之前的答案,然后现在的答案(up和down的和)减去进入u之前的答案才是u的答案。
#include<bits/stdc++.h>
using namespace std;
const int N = 300000 + 10;
const int P = 20;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<='0'&&ch>='9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m;
int w[N];
vector<int> qs[N],qt[N],ed[N];
int up[N],down[N];
struct node{
int pre,v;
}e[N<<1];
int num=0,head[N];
void addedge(int from,int to){
e[++num].pre=head[from],e[num].v=to;
head[from]=num;
}
int anc[N][P],dep[N];
void dfs(int u,int f){
anc[u][0]=f;
for(int i=1;i<P;++i) anc[u][i]=anc[anc[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].pre){
int v=e[i].v;
if(v==f) continue;
dep[v]=dep[u]+1;
dfs(v,u);
}
}
int query(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=0,t=dep[u]-dep[v];t;t>>=1,++i)
if(t&1) u=anc[u][i];
if(u==v) return u;
for(int i=P-1;i>=0;--i)
if(anc[u][i]!=anc[v][i]) u=anc[u][i],v=anc[v][i];
return anc[u][0];
}
int cnts[N],cnt[N];
void dfs1(int u,int f){
int pre=up[dep[u]+w[u]]+down[dep[u]-w[u]+n];
for(int i=0;i<ed[u].size();++i) ++down[ed[u][i]];
up[dep[u]]+=cnts[u];
for(int i=head[u];i;i=e[i].pre){
int v=e[i].v;
if(v==f) continue;
dfs1(v,u);
}
cnt[u]=up[dep[u]+w[u]]+down[dep[u]-w[u]+n]-pre;
for(int i=0;i<qs[u].size();++i) {
--up[qs[u][i]];
if(qs[u][i]==dep[u]+w[u]) --cnt[u];
}
for(int i=0;i<qt[u].size();++i) --down[qt[u][i]];
}
int main(){
n=read(),m=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
addedge(u,v),addedge(v,u);
}
for(int i=1;i<=n;++i) w[i]=read();
dfs(1,1);
for(int i=1;i<=m;++i){
int s=read(),t=read();
int lca=query(s,t),dis=dep[s]+dep[t]-2*dep[lca];
++cnts[s];
ed[t].push_back(dep[t]-dis+n);
qs[lca].push_back(dep[s]);
qt[lca].push_back(dep[t]-dis+n);
}
dfs1(1,1);
for(int i=1;i<=n;++i) printf("%d ",cnt[i]);
return 0;
}
换教室。
一道比较水的期望dp,dp[i][j][0]表示前i段时间有j段时间换课,且第i节课换/不换期望的最小劳累值,稍微理解一下期望应该就可以做了,存路径什么的细节要注意。
大概是目前写过最长的dp方程。
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N = 300 + 10;
const int NN = 2000 + 10;
const int inf = 0x73f3f3f;
inline double Min(double a,double b) {return a<b?a:b;}
int n,m,v,e;
int dis[N][N],c[NN],d[NN];
double dp[NN][NN][2],k[NN];
int main(){
n=read(),m=read(),v=read(),e=read();
for(int i=1;i<=n;++i) c[i]=read();
for(int i=1;i<=n;++i) d[i]=read();
memset(dis,63,sizeof(dis));
for(int i=1;i<=n;++i) scanf("%lf",&k[i]);
for(int i=1;i<=e;++i){
int u=read(),v=read();
dis[v][u]=dis[u][v]=min(dis[u][v],read());
}
for(int i=1;i<=v;++i) dis[i][i]=0;
for(int k=1;k<=v;++k)
for(int i=1;i<=v;++i)
for(int j=1;j<=v;++j)
dis[j][i]=dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for(int i=1;i<=n;++i){
for(int j=0;j<=m;++j)
dp[i][j][0]=dp[i][j][1]=1.0*inf;
}
dp[1][1][1]=0,dp[1][0][0]=0;
for(int i=2;i<=n;++i){
for(int j=0;j<=m;++j){
if(j!=0) dp[i][j][1]=Min(dp[i][j][1],dp[i-1][j-1][1]+k[i-1]*k[i]*1.0*dis[d[i-1]][d[i]]+k[i-1]*(1-k[i])*1.0*dis[d[i-1]][c[i]]+(1-k[i-1])*k[i]*1.0*dis[c[i-1]][d[i]]+(1-k[i-1])*(1-k[i])*1.0*dis[c[i-1]][c[i]]);
if(j!=0) dp[i][j][1]=Min(dp[i][j][1],dp[i-1][j-1][0]+k[i]*1.0*dis[c[i-1]][d[i]]+(1-k[i])*1.0*dis[c[i-1]][c[i]]);
dp[i][j][0]=Min(dp[i][j][0],dp[i-1][j][1]+k[i-1]*1.0*dis[d[i-1]][c[i]]+(1-k[i-1])*1.0*dis[c[i-1]][c[i]]);
dp[i][j][0]=Min(dp[i][j][0],dp[i-1][j][0]+dis[c[i-1]][c[i]]);
}
}
double ans=1.0*inf;
for(int i=0;i<=m;++i) ans=Min(ans,Min(dp[n][i][0],dp[n][i][1]));
printf("%.2lf\n",ans);
return 0;
}
组合数问题
递推求组合数+前缀和。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2000 + 10;
int k,t,n,m;
int c[N][N];
ll ans[N][N];
int main(){
scanf("%d%d",&t,&k);
for(int i=0;i<=2000;++i){
for(int j=0;j<=i;++j){
if(j==0||j==i) c[i][j]=1;
else c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
if(c[i][j]==0) ans[i][j]=ans[i][j-1]+1;
else ans[i][j]=ans[i][j-1];
}
}
while(t--){
scanf("%d%d",&n,&m);
ll cnt=0;
for(int i=0;i<=n;++i) cnt+=ans[i][min(i,m)];
printf("%d\n",cnt);
}
return 0;
}
蚯蚓
80分的堆
这个还是很好想的。
主要就是增长长度的处理,我是用一个flag记录到目前,如果一次都没有砍掉的蚯蚓应该增长的长度(其实可以直接由i推得),砍断后将新得到的蚯蚓的长度减去目前的flag就行了,相对长度并不会改变。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N = 10000000 + 10;
int n,m,q,u,v,t;
int cnt;
ll a[N];
bool cmp(int a,int b){
return a>b;
}
int main(){
n=read(),m=read(),q=read(),u=read(),v=read(),t=read();
for(register int i=1;i<=n;++i) a[i]=read();
make_heap(a+1,a+n+1);
cnt=n;ll flag=0;
for(int i=1;i<=m;++i){
if(i%t==0) printf("%lld ",a[1]+flag);
ll x=(a[1]+flag)*u/v,y=(a[1]+flag)-x;flag+=q;x-=flag,y-=flag;
pop_heap(a+1,a+cnt+1);
a[cnt]=x;push_heap(a+1,a+cnt+1);
a[++cnt]=y;push_heap(a+1,a+cnt+1);
}
printf("\n");
sort(a+1,a+cnt+1,cmp);
for(int i=1;i<=cnt;++i){
if(i%t==0) printf("%lld ",a[i]+flag);
}
return 0;
}
100分
维护三个单调队列q1,q2,q3。
其中q1存蚯蚓原始的长度,q2存px,q3存x-px。
每次从三个队列中取出最长的蚯蚓,然后把它直接排在q2和q3的队尾。
为什么可以直接排在队尾?这就需要证明砍掉后的蚯蚓仍然具有单调性。
设蚯蚓原始的长度为x,y,且x>y,显然x会比y先砍,所以x砍完后会比y先入队。
则砍断后的长度分别为px+qt,x-px+qt,yp+qtp,y+qt-yp-qtp
显然后面的两个比前面的两个小,满足单调。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N = 100000 + 10;
const ll inf = 233333333333333LL;
int n,m,q,u,v,t;
ll a[N];
deque<ll> q1,q2,q3;
int getans(){
ll mmax=-inf;
if(!q1.empty()) mmax=max(mmax,q1.front());
if(!q2.empty()) mmax=max(mmax,q2.front());
if(!q3.empty()) mmax=max(mmax,q3.front());
if(!q1.empty()&&q1.front()==mmax) q1.pop_front();
else if(!q2.empty()&&q2.front()==mmax) q2.pop_front();
else q3.pop_front();
return mmax;
}
int main(){
n=read(),m=read(),q=read(),u=read(),v=read(),t=read();
for(int i=1;i<=n;++i) a[i]=read();
sort(a+1,a+n+1);
for(int i=n;i>=1;--i) q1.push_back(a[i]);
ll flag=0;
for(int i=1;i<=m;++i){
ll mmax=getans();
if(i%t==0) printf("%lld ",mmax+flag);
ll x=(mmax+flag)*u/v,y=mmax+flag-x;
flag+=q,x-=flag,y-=flag;
q2.push_back(x),q3.push_back(y);
}
printf("\n");
for(int i=1;i<=n+m;++i){
ll ans=getans();
if(i%t==0) printf("%lld ",ans+flag);
}
return 0;
}
愤怒的小鸟
65
其实这个做法有问题
我是枚举d,j,k(都在集合i中),如果它们可以用一条抛物线全部击倒,则相当于i集合的答案可以和从i这个集合中除开d/j/k的答案一样,但如果本来在i除开k的那个集合中,用一条抛物线击中i和j并不是最优的,(即i和j被两条抛物线分别击中),就会少算答案。
#include<bits/stdc++.h>
using namespace std;
const int N = 18;
const double eps = 1e-10;
int n,m;
double x[N+5],y[N+5];
int dp[(1<<N)+5];
double a[(1<<N)+5],b[(1<<N)+5];
void getab(double &a,double &b,int i,int j){
double x1=x[i],x2=x[j],y1=y[i],y2=y[j];
a=1.0*y1*x2-1.0*y2*x1;
if(1.0*x1*x1*x2-1.0*x2*x2*x1==0.0) a=0;
else a=1.0*a/(1.0*x1*x1*x2-x2*x2*x1);
b=1.0*(y1-1.0*a*x1*x1)/(1.0*x1);
}
int t;
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]);
memset(dp,63,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;++i){
dp[1<<i-1]=1;
for(int j=i+1;j<=n;++j){
a[(1<<i-1)|(1<<j-1)]=b[(1<<i-1)|(1<<j-1)]=0;
if(fabs(x[i]-x[j])<eps) continue;
double aa,bb;getab(aa,bb,i,j);
if(aa<(-eps)) dp[(1<<i-1)|(1<<j-1)]=1,a[(1<<i-1)|(1<<j-1)]=aa,b[(1<<i-1)|(1<<j-1)]=bb;
else dp[(1<<i-1)|(1<<j-1)]=2;
}
}
int tot=(1<<n)-1;
for(int i=1;i<=tot;++i){
for(int j=1;j<=n;++j){
if((i|(1<<j-1))!=i) continue;
for(int d=j+1;d<=n;++d){
if((i|(1<<d-1))!=i) continue;
for(int zz=d+1;zz<=n;++zz){
if((i|(1<<zz-1))!=i) continue;
if(a[(1<<d-1)|(1<<j-1)]<(-eps)&&fabs(a[(1<<d-1)|(1<<j-1)]*x[zz]*x[zz]+b[(1<<d-1)|(1<<j-1)]*x[zz]-y[zz])<eps)
dp[i]=min(dp[i],min(dp[i^(1<<d-1)],dp[i^(1<<j-1)])),dp[i]=min(dp[i],dp[i^(1<<zz-1)]);
else {
dp[i]=min(dp[i],min(dp[i^(1<<j-1)^(1<<zz-1)]+dp[(1<<zz-1)|(1<<j-1)],dp[i^(1<<d-1)^(1<<j-1)]+dp[(1<<d-1)|(1<<j-1)]));
dp[i]=min(dp[i],dp[i^(1<<d-1)^(1<<zz-1)]+dp[(1<<d-1)|(1<<zz-1)]);
}
}
}
}
}
printf("%d\n",dp[tot]);
}
return 0;
}
100分
我们再用一个数组f[i]表示用某一条抛物线可以击中的小鸟的集合。
所以dp[i|f[j]]=min(dp[i|f[j]],dp[i]+1).
这个做法好机智。。可以让cnt输出看一下,其实f数组的范围远不到n^2,所以可过。
#include<bits/stdc++.h>
using namespace std;
const int N = 18;
const double eps = 1e-10;
int n,m,cnt=0;
double x[N+5],y[N+5];
int dp[(1<<N)+5],f[(1<<N)+5];
void getab(double &a,double &b,int i,int j){
double x1=x[i],x2=x[j],y1=y[i],y2=y[j];
a=1.0*y1*x2-1.0*y2*x1;
if(1.0*x1*x1*x2-1.0*x2*x2*x1==0.0) a=0;
else a=1.0*a/(1.0*x1*x1*x2-x2*x2*x1);
b=1.0*(y1-1.0*a*x1*x1)/(1.0*x1);
}
int t;
void update(){
cnt=0;memset(dp,63,sizeof(dp));
}
int main(){
scanf("%d",&t);
while(t--){
update();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]);
for(int i=1;i<=n;++i){
f[++cnt]=(1<<i-1);
for(int j=i+1;j<=n;++j){
if(fabs(x[i]-x[j])<eps) continue;
double a,b;getab(a,b,i,j);
if(a<-eps) {
int tot=((1<<i-1)|(1<<j-1));
for(int k=j+1;k<=n;++k)
if(fabs(1.0*a*x[k]*x[k]+b*x[k]-y[k])<eps) tot|=(1<<k-1);
f[++cnt]=tot;
}
}
}
int tot=(1<<n)-1;
dp[0]=0;
for(int i=0;i<=tot;++i){
for(int j=1;j<=cnt;++j){
dp[i|f[j]]=min(dp[i|f[j]],dp[i]+1);
}
}
printf("%d\n",dp[tot]);
}
return 0;
}