【解题报告】NOIP2014
Day1
T1 生活大爆炸版石头剪刀布
思路
模拟
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
int n,na,nb;
int a[205],b[205];
int sa,sb;
int score[10][10]={{0,0,1,1,0},{1,0,0,1,0},{0,1,0,0,1},{0,0,1,0,1},{1,1,0,0,0}};
int main()
{
cin>>n>>na>>nb;
for(int i=0;i<na;i++)
cin>>a[i];
for(int i=0;i<nb;i++)
cin>>b[i];
for(int i=0;i<n;i++)
{
sa+=score[a[i%na]][b[i%nb]];
sb+=score[b[i%nb]][a[i%na]];
}
cout<<sa<<" "<<sb<<endl;
return 0;
}
T2 联合权值
思路
我们可以发现,我们对于每个点可以枚举两个出点,这样距离就为2,然后我们枚举找到两个权值最大的点,这两个点产生的联合权值就最大
那么这两个点的联合权值怎么算呢
我们可以使用平方和公式
\[(a+b)^2=a^2+2ab+b^2
\]
移项,就得到了
\[2ab=(a+b)^2-(a^2+b^2)
\]
对于多个点,我们也可以推出高维的结论
和这个平方差的结论类似
然后我们就直接搜就可以了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int mod=10007;
const int maxn=200005;
struct edge{
int e,next;
}ed[maxn*2];
int en,first[maxn];
void add_edge(int s,int e)
{
en++;
ed[en].next=first[s];
first[s]=en;
ed[en].e=e;
}
int n;
int val[maxn];
int ans1,ans2;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n-1;i++)
{
int u,v;
cin>>u>>v;
add_edge(u,v);
add_edge(v,u);
}
for(int i=1;i<=n;i++)
cin>>val[i];
for(int i=1;i<=n;i++)
{
int mx1=-99999,mx2=-99999;
int sq=0,sum=0;
for(int j=first[i];j;j=ed[j].next)
{
int e=ed[j].e;
if(mx1<val[e])
{
mx2=mx1;
mx1=val[e];
}
else if(mx2<val[e])
{
mx2=val[e];
}
sum=(sum+val[e])%mod;
sq=(sq+val[e]*val[e]%mod);
}
sum=sum*sum%mod;
ans2=(ans2+sum-sq+mod)%mod;
ans1=max(ans1,mx1*mx2);
}
cout<<ans1<<" "<<ans2<<'\n';
return 0;
}
T3 飞扬的小鸟
思路
动态规划,一个背包类的模板
设 \(f[i][j]\) 表示横坐标为 \(i\) 的时候,高度为 \(j\) ,这个时候最小点击次数是多少
上升的时候呢,就是一个完全背包
下降的时候就是01背包
如果纵坐标超过 \(m\) 的话,那我们就直接把纵坐标变成 \(m\)
From:https://www.luogu.com.cn/blog/JOE/solution-p1941
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int maxn=10005;
const int INF=0x3f3f3f;
int n,m,k;
int x[maxn],y[maxn];
int down[maxn],up[maxn];
int f[maxn][2005];
bool flag[maxn];
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i];
up[i]=m;
down[i]=1;
}
for(int i=1;i<=k;i++)
{
int a,b,c;
cin>>a>>b>>c;
flag[a]=1;
down[a]=b+1;
up[a]=c-1;
}
memset(f,0x3f3f3f,sizeof(f));
for(int i=1;i<=m;i++)
f[0][i]=0;
for(int i=1;i<=n;i++)
{
for(int j=x[i]+1;j<=x[i]+m;j++)
f[i][j]=min(f[i-1][j-x[i]]+1,f[i][j-x[i]]+1);
for(int j=m+1;j<=x[i]+m;j++)
f[i][m]=min(f[i][j],f[i][m]);
for(int j=1;j<=m-y[i];j++)
f[i][j]=min(f[i][j],f[i-1][j+y[i]]);
for(int j=1;j<down[i];j++)
f[i][j]=INF;
for(int j=up[i]+1;j<=m;j++)
f[i][j]=INF;
}
int ans=INF;
for(int i=1;i<=m;i++)
ans=min(ans,f[n][i]);
if(ans<INF)
{
cout<<1<<'\n'<<ans<<'\n';
return 0;
}
int i,j;
for(i=n;i>=1;i--)
{
for(j=1;j<=m;j++)
if(f[i][j]<INF)
break;
if(j<=m)
break;
}
ans=0;
for(j=1;j<=i;j++)
if(flag[j])
ans++;
cout<<0<<'\n'<<ans<<'\n';
return 0;
}
Day2
T1 无线网路发射器选址
思路
这个的话,直接前前缀和+模拟不就好了吗
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
const int maxn=150;
int d,n;
int m[maxn][maxn];
long long ans1=-999999,ans2;
int main()
{
cin>>d>>n;
for(int i=1;i<=n;i++)
{
int x,y,k;
cin>>x>>y>>k;
m[x][y]=k;
}
long long mx=0;
for(int i=0;i<=128;i++)
{
for(int j=0;j<=128;j++)
{
int x1,x2,y1,y2;
mx=0;
if(i-d<0)
x1=0;
else
x1=i-d;
if(i+d>128)
x2=128;
else
x2=i+d;
if(j-d<0)
y1=0;
else
y1=j-d;
if(j+d>128)
y2=128;
else
y2=j+d;
for(int a=x1;a<=x2;a++)
{
for(int b=y1;b<=y2;b++)
mx+=m[a][b];
}
if(mx>ans1)
ans1=mx,ans2=1;
else if(mx==ans1)
ans2++;
else
continue;
}
}
cout<<ans2<<" "<<ans1<<'\n';
return 0;
}
T2 寻找道路
思路
本来一个是一个裸的单源最短路径
没想到还是穿了个内裤的
首先我们要直接或者间接与终点连通的,所以我们建一个反图,标记终点可以到哪些点
然后我们如果标记到起点的话,说明可以,反之不可以
如果可以的话,我们在一开始的时候因为也要建正图
所以从起点开始做一个单源最短路径就好了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int maxn=300005;
struct edge{
int e,next;
}ed[maxn*2];
int en,first[maxn];
void add_edge(int s,int e)
{
en++;
ed[en].next=first[s];
first[s]=en;
ed[en].e=e;
}
int n,m,s,t;
int d[maxn];
bool qyj[maxn],qy[maxn],yj[maxn];
void spfa(int s)
{
memset(qyj,false,sizeof(qyj));
memset(d,0x3f3f3f,sizeof(d));
queue <int> q;
q.push(s);
qyj[s]=true;
d[s]=0;
while(q.size())
{
int x=q.front();
q.pop();
qyj[s]=false;
for(int i=first[x];i;i=ed[i].next)
{
int e=ed[i].e;
if(d[e]>d[x]+1&&qy[e])
{
d[e]=d[x]+1;
if(!qyj[e])
{
qyj[e]=true;
q.push(e);
}
}
}
}
}
void bfs(int t)
{
memset(qyj,false,sizeof(qyj));
queue <int> q;
q.push(t);
qy[t]=qyj[t]=true;
while(q.size())
{
int x=q.front();
q.pop();
for(int i=first[x];i;i=ed[i].next)
{
int e=ed[i].e;
if(!qyj[e])
{
qyj[e]=true;
qy[e]=true;
q.push(e);
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
add_edge(x,y);
}
cin>>s>>t;
bfs(t);
for(int i=1;i<=n;i++)
yj[i]=qy[i];
for(int i=1;i<=n;i++)
{
if(!yj[i])
{
for(int j=first[i];j;j=ed[j].next)
{
int e=ed[j].e;
if(yj[e])
yj[e]=false;
}
}
}
spfa(t);
if(d[s]>=0x3f3f3f)
cout<<-1<<'\n';
else
cout<<d[s]<<'\n';
return 0;
}
T3 解方程
思路
大概就是依次尝试一下成不成立吧
我们我们在输入的时候用秦九昭算法来处理一下
我们考虑到,我们每一个系数都模一个质数这样就好了
为什么呢,实际上比较玄学
但是有一定的严谨性
我们可以对多个质数取模,这样就能大大降低错误的概率
然后算这个表达式的时候也是边乘边模就好了,记住,要模之前的那个数字
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#define int long long
using namespace std;
const int p=1000000007;
const int maxn=1005;
int a[maxn],ans[1000003];
int n,m;
int en;
bool t=true;
inline int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=((x<<1)+(x<<3)+c-'0')%p;
c=getchar();
}
return x*f;
}
int f(int x)
{
int res=0;
for(int i=n;i>=1;i--)
res=((res+a[i])*x)%p;
res=(res+a[0])%p;
return res==0;
}
signed main()
{
n=read(),m=read();
for(int i=0;i<=n;i++)
a[i]=read();
for(int i=1;i<=m;i++)
{
if(f(i))
{
t=false;
ans[0]++;
en++;ans[en]=i;
}
}
if(t)
{
cout<<ans[0]<<endl;
return 0;
}
cout<<ans[0]<<endl;
for(int i=1;i<=en;i++)
cout<<ans[i]<<endl;
return 0;
}