day1
T1 玩具谜题
https://www.luogu.org/problemnew/show/P1563
本题主要考察两个问题
1、你是否会编程
2、你是否会取模
随便水水就过去了
#include
#include
#include
#include
#include
using namespace std;
const int MAXN=1e5+5,INF=1e9+5;
typedef long long ll;
int n,m;
struct toy
{
int d;
char s[15];
}toy[MAXN];
int main()
{
//freopen("toy.in","r",stdin);
//freopen("toy.out","w",stdout);
std::scanf("%d%d",&n,&m);
for(int i=0;i
T2 天天爱跑步
https://www.luogu.org/problemnew/show/P1600
这道题的难度应该是今年来的所有noip中最难之一的吧(个人感觉从11年开始最难的三道题就是12年day2的疫情控制、这道题还有17年day2的宝藏)
思路是倍增/tarjan/链剖求lca,然后利用lca、树深度的特殊性质查询每个查询上子树的点满足的条件,然后用一个桶来装并统计,然后就再减去其lca的贡献值(虽然说很多标程上都说这个是差分,但是实际上我觉得和差分还是有一点区别,虽然说确实要用到差分的思想),当然这么说好像有些抽象,所以我们来稍微详细地讲一下这道题
每一条路径我们都可以把其看成两段,一段是起始点u到lca的路径,另一段则是lca到v的路径
然后我们可以看到,在u到lca的路径上的任意一个节点x,当deep[u]-deep[x]=w[x]时,u会对x点有贡献,移项过后我们就可以得到deep[u]=deep[x]+w[x],所以我们可以直接开一个桶bac[],用dfs暴力统计一遍满足条件的点,减去lca的贡献就搞定了
// luogu-judger-enable-o2
#include
#include
#include
#define RG register
const int MAXN=6e5+5;
const int P=17+1;
char buffer[MAXN],*S=buffer,*T=buffer;
inline int read()
{
char ch=getchar();int sum=0;
while(!(ch>='0'&&ch<='9'))ch=getchar();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=getchar();
return sum;
}
void print(int x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)print(x/10);
putchar(x%10+'0');
}
int n,m;
struct Edge
{
int nxt;
int to;
}edge[MAXN<<1];
int num;
int head[MAXN];
void add(int from,int to)
{
edge[++num].nxt=head[from];
edge[num].to=to;
head[from]=num;
}
int dist[MAXN];
struct Chain
{
int from;
int LCA;
int len;
int to;
}mp[MAXN];
int anc[MAXN][P+1];
int dep[MAXN];
int w[MAXN];
int ans[MAXN];
std::vectorG[MAXN];
std::vectorV[MAXN];
int D[MAXN<<1];
int cnt[MAXN];
int Mark[MAXN];
void dfs1(int u,int f)
{
anc[u][0]=f;
for(RG int p=1;p<=P;p++)
{
anc[u][p]=anc[anc[u][p-1]][p-1];
}
for(RG int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==f)
{
continue;
}
dep[v]=dep[u]+1;
dist[v]=dist[u]+1;
dfs1(v,u);
}
}
inline int lca(int u,int v)
{
if(dep[u]>=1,p++)
{
if(t&1)
{
u=anc[u][p];
}
}
if(u==v) return u;
for(RG int p=P;p>=0;p--)
{
if(anc[u][p]!=anc[v][p])
{
u=anc[u][p];
v=anc[v][p];
}
}
return anc[u][0];
}
void Dfs(int x,int f)
{
int cc=D[dep[x]+w[x]],co=D[w[x]-dep[x]+MAXN];
for(RG int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==f) continue;
Dfs(v,x);
}
D[dep[x]]+=Mark[x];
for(RG int i=0;i
day1 T3 换教室
https://www.luogu.org/problemnew/show/P1850
从实现难度上来讲甚至都不能称得上是noip提高组的题。。。只要你知道期望dp的基本套路就能直接做出来,方程思路都清晰明了而且实现相当简单,几个for循环丢进去就能轻松ac
我们先定义一下状态,状态时dp[i][j][0/1]的一个三维数组,第一维i表示前i间教室,j表示目前为止已经申请了j次更换(算上这件教室),第三维的0/1则分别表示这次这件教室申不申请更换
那么我们可以轻松地得到状态转移方程(虽然很轻松,但是方程又丑又长,而且还容易写错)
当然这道题里还要注意两个点,第一,这张图没有跟你说直接连的边是最短的,所以得先用Floyed跑一遍任意两点间的最短路g[i][j]。
第二,这道题可以在同一间教室连边(蠢炸了。。。。。),所以在建边的时候你要判等。关键是如果你忽略了这一点,这道题你大概会丢掉14个左右的点(出题人估计也是知道这道题太简单了,所以故意挖一些坑给人跳)
dp[i][j][0]=std::min(dp[i-1][j][0]+g[c[i-1]][c[i]],dp[i-1][j][1]+g[c[i-1]][d[i]]*k[i]+g[c[i-1]][c[i]]*(1-k[i]));(这个方程其实挺短的,接下来才是最蛋疼的)
dp[i][j][1]=std::min(dp[i][j][1],dp[i-1][j-1][0]+g[c[i-1]][d[i]]*k[i]+(1-k[i])*g[c[i-1]][c[i]]+dp[i-1][j-1][1]+g[d[i-1]][d[i]]*k[i-1]*k[i]+g[c[i-1]][d[i]]*(1-k[i-1])*k[i]+g[d[i-1]][c[i]]*k[i-1]*(1-k[i])+g[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]))(方程的意思真心很简单,但是就是能够写到人心态爆炸)
#include
#include
#include
const int MAXN=2005;
const int MAXM=305;
const double INF=1e9+7;
int ln,lm,n,m;
int c[MAXN];
int d[MAXN];
double k[MAXN];
int G[MAXM][MAXM];
double dp[MAXN][MAXN][2];
void floyed()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
G[i][j]=std::min(G[i][j],G[i][k]+G[k][j]);
}
}
}
}
void solve()
{
for(int i=1;i<=ln;i++)
for(int j=0;j<=lm;j++)
{
dp[i][j][0]=dp[i][j][1]=INF;
}
dp[0][0][0]=0;
for(int i=1;i<=ln;i++)
{
for(int j=0;j<=lm;j++)
{
dp[i][j][0]=std::min(dp[i-1][j][0]+G[c[i-1]][c[i]],dp[i-1][j][1]+1.0*G[d[i-1]][c[i]]*k[i-1]+G[c[i-1]][c[i]]*(1-k[i-1]));
if(j>0)
{
dp[i][j][1]=dp[i-1][j-1][0]+1.0*G[c[i-1]][d[i]]*k[i]+1.0*G[c[i-1]][c[i]]*(1-k[i]);
}
if(j>1)
{
dp[i][j][1]=std::min(dp[i][j][1],dp[i-1][j-1][1]+k[i-1]*(G[d[i-1]][d[i]]*k[i]+G[d[i-1]][c[i]]*(1-k[i]))+(1-k[i-1])*(G[c[i-1]][d[i]]*k[i]+G[c[i-1]][c[i]]*(1-k[i])));
}
}
}
}
int main()
{
//freopen("classroom.in","r",stdin);
//freopen("classroom.out","w",stdout);
std::scanf("%d%d%d%d",&ln,&lm,&n,&m);
for(int i=1;i<=ln;i++)
{
std::scanf("%d",c+i);
}
for(int i=1;i<=ln;i++)
{
std::scanf("%d",d+i);
}
for(int i=1;i<=ln;i++)
{
std::scanf("%lf",k+i);
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
G[i][j]=G[j][i]=INF;
}
}
for(int i=1;i<=m;i++)
{
int u,v,w;
std::scanf("%d%d%d",&u,&v,&w);
if(u!=v)
{
G[u][v]=G[v][u]=std::min(G[u][v],w);
}
}
floyed();
solve();
double ans=INF;
for(int j=0;j<=lm;j++)
{
ans=std::min(ans,std::min(dp[ln][j][0],dp[ln][j][1]));
}
std::printf("%.2f",ans);
return 0;
}
day2 T1 组合数问题
https://www.luogu.org/problemnew/show/P2822
O(n^2)预处理,O(1)查询,注意n、m的大小关系,只要懂点儿数论都会做
#include
#include
const int MAXN=2005;
int t,k;
int c[MAXN][MAXN];
int s[MAXN][MAXN];
void prepare()
{
for(int i=0;i<=2000;i++)
{
c[i][0]=1;
}
c[1][1]=1;
for(int i=2;i<=2000;i++)
{
for(int j=1;j<=2000;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
}
}
for(int i=1;i<=2000;i++)
{
for(int j=1;j<=i;j++)
{
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
if(c[i][j]==0)
{
s[i][j]+=1;
}
}
s[i][i+1]=s[i][i];
}
}
int main()
{
//std::freopen("problem.in","r",stdin);
//std::freopen("problem.out","w",stdout);
std::scanf("%d%d",&t,&k);
prepare();
while(t--)
{
int n,m;
std::scanf("%d%d",&n,&m);
if(m>n) m=n;
std::printf("%d\n",s[n][m]);
}
return 0;
}
T2 蚯蚓
https://www.luogu.org/problemnew/show/P2827
因为比例跟初始数据的大小是一定的,所以我们可以发现每一次分出来的数据中,原数据中大的,剪下来放到另外一个数组后依然是更大的,所以我们用三个递减的单调队列来模拟一边过程就能够输出正确的结果
// luogu-judger-enable-o2
#include
#include
#include
const int MAXN=7e6+5;
int n,m,q,u,v,t;
int a[MAXN];
int q1[MAXN];
int q2[MAXN];
int q3[MAXN];
int h1=1;
int t1=0;
int h2=1;
int t2=0;
int h3=1;
int t3=0;
bool cmp(const int &a,const int &b)
{
return a>b;
}
int main()
{
std::scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
double p=u*1.0/v;
for(int i=1;i<=n;i++)
{
std::scanf("%d",a+i);
q1[i]=a[i];
q1[i]+=m*q;
}
std::sort(q1+1,q1+1+n,cmp);
int num1=m/t;
int num2=(n+m)/t;
t1=n;
for(int i=1;i<=m;i++)
{
int a=0;
int b=0;
int c=0;
if(h1<=t1)
{
a=q1[h1];
}
if(h2<=t2)
{
b=q2[h2];
}
if(h3<=t3)
{
c=q3[h3];
}
int d=std::max(a,std::max(b,c));
if(a==d&&h1<=t1)
{
h1++;
}
else
if(b==d&&h2<=t2)
{
h2++;
}
else
h3++;
d-=(m-i+1)*q;
if(i%t==0&&num1)
{
std::printf("%d ",d);
num1--;
}
int z1=floor(d*p);
int z2=d-z1;
if(z1>z2)
{
q2[++t2]=z1+(m-i)*q;
q3[++t3]=z2+(m-i)*q;
}
else
{
q2[++t2]=z2+(m-i)*q;
q3[++t3]=z1+(m-i)*q;
}
}
std::printf("\n");
int i=0;
while(num2)
{
++i;
int a=0;
int b=0;
int c=0;
if(h1<=t1) a=q1[h1];
if(h2<=t2) b=q2[h2];
if(h3<=t3) c=q3[h3];
int d=std::max(a,std::max(b,c));
if(i%t==0&&num2)
{
printf("%d ",d);
num2--;
}
if(a==d&&h1<=t1)++h1;
else if(b==d&&h2<=t2)++h2;
else ++h3;
}
return 0;
}
T3 愤怒的小鸟
状压dp或者dfs都能过,但是值得注意的一点是状压的时候,要更新的状态不要一个一个去枚举,查找到第一个没有被更新的猪在用它来更新,这样可以防止被卡常。
#include
#include
#include
#include
const int MAXN=1<<19-1;
int dp[1<<19-1];
int g[19][19];
double x[MAXN];
double y[MAXN];
int main()
{
int T;
std::scanf("%d",&T);
while(T--)
{
std::memset(g,0,sizeof(g));
std::memset(dp,127,sizeof(dp));
dp[0]=0;
int n,m;
std::scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
std::scanf("%lf%lf",&x[i],&y[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
/*
double x1=x[i];
double y1=y[i];
double x2=x[j];
double y2=y[j];
double a=(y2*x1-y1*x2)/(x1*x2*(x2-x1));
double b=(y2*x1*x1-y1*x2*x2)/(x1*x2*(x1-x2));
*/
double x1=x[i]*x[i]*x[j];
double x2=x[i]*x[j]*x[j];
double y1=y[i]*x[j];
double y2=y[j]*x[i];
double a=(y2-y1)/(x2-x1);
double b=(y[i]-a*x[i]*x[i])/x[i];
if(b>0&&a<0)
{
for(int k=1;k<=n;k++)
{
if(fabs(x[k]*x[k]*a+x[k]*b-y[k])<1e-7)
{
g[i][j]=g[i][j]|(1<<(k-1));
}
}
}
}
}
for(int s=0;s<=(1<>(t-1))&1)
{
t++;
}
dp[s|1<<(t-1)]=std::min(dp[s|1<<(t-1)],dp[s]+1);
for(int j=t+1;j<=n;j++)
{
dp[s|g[t][j]]=std::min(dp[s|g[t][j]],dp[s]+1);
}
}
std::printf("%d\n",dp[(1<
总的来说day1难于day2吧。。。day2的题真正基础扎实的人拿到240分以上都不难