NOIP2012Day1
一、Vigenère 密码
数据:密钥长度∈[1,100],密文长度∈[1,1000]
很简单的一道题,只要对着推一下公式就行了。
二、国王游戏
数据:
对于 20%,n∈[1,10]
对于 60%,n∈[1,100],保证答案不超过 109
对于 100%,n∈[1,1,000]
20%的暴力全排就行了
答案要超long long,所以要高精度
正解是贪心。对于一个数Num(i),他的奖金是包括自己前面所有人的L的积去除以自己左右手上的数字。那么只要按照每个人的左右手乘积排个序就行了。
当乘积相同的时候,L小的在前面,这样保持乘积最小。
Code:
#include
#define M 10000
#define ll long long
using namespace std;
struct node{ll l,r;}A[1005];
bool cmp(node x,node y){//按照左右手乘积排序
if(x.l*x.r==y.l*y.r)return x.l>y.l;
return x.l*x.rstruct bignum{//高精度
int num[1005];
int len;
bignum(){memset(num,0,sizeof num);len=1;}
bignum operator *(ll x){//高精*低精
bignum tmp;
tmp.len=len;
for(int i=1;i<=len;i++){
tmp.num[i]+=num[i]*x;
tmp.num[i+1]+=tmp.num[i]/M;
tmp.num[i]%=M;
}
while(tmp.num[tmp.len+1])tmp.len++;
return tmp;
}
bignum operator /(ll x){//高精/低精
bignum tmp;
tmp.len=len;
for(int i=1;i<=len;i++)tmp.num[i]=num[i];
for(int i=len;i>=1;i--){
if(i>1)tmp.num[i-1]+=tmp.num[i]%x*M;
tmp.num[i]=tmp.num[i]/x;
}
while(tmp.num[tmp.len]==0&&tmp.len>1)tmp.len--;
return tmp;
}
bool operator <(bignum x){//重载小于运算符
if(len!=x.len)return lenlen;
for(int i=len;i>=1;i--)if(num[i]!=x.num[i])return num[i]return 0;
}
void Print(){
printf("%d",num[len]);
for(int i=len-1;i>=1;i--)printf("%04d",num[i]);
puts("");
}
};
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<=n;i++)scanf("%lld%lld",&A[i].l,&A[i].r);
sort(A+1,A+1+n,cmp);
bignum h,mx;h.num[1]=1;h=h*A[0].l;
for(int i=1;i<=n;i++){//直接计算
if(mxreturn 0;
}
三、开车旅行
数据:
对于 70%,N∈[1,1e3],M∈[1,1e5]
对于 100%,N∈[1,1e5],M∈[1,1e5]
这道题暴力可以过70分,只要把每个点往后一个和两个的城市全部预处理出来就行了。这样就只用nm了。
这道题也可以分成两个问题:
1、快速求一个城市往后一个、两个的城市
2、快速求往后一段距离可以行走的距离
对于1,暴力的解法是n^2,把所有后面的点都判断一遍。实际上真正会影响到的点只有四个。这四个是在用了数据结构后的说法。对于每一个点,和他距离相近的两个肯定位于这个点往后两个和往前两个。可以用set,multiset,双向链表等。想法也很简单,倒着把每个数放进去,这样可以保持方向的一致,然后用一些可以保持高度从小到大顺序的数据结构就行了。预处理就成了n log n。
对于2,可以用倍增。倍增的时候要把小A和小B“绑起来”,看做一个人来计算,但是存距离的时候要分开来,因为要分开来算。计算就和以前的一模一样。
Code:
#include
#define M 100005
#define ll long long
using namespace std;
int n,H[M],nxt[2][M];
struct node{
int x,id;
bool operator < (const node &s)const{
return xint dis(int x,int y){
if(y==0)return 2e9;
return int(abs(H[x]-H[y]));
}
set Set;
set ::iterator it;
void solve(int i){//四条判断,也可以存起来然后用sort
if(dis(i,(*it).id)0][i]))nxt[1][i]=nxt[0][i],nxt[0][i]=(*it).id;
else if(dis(i,(*it).id)==dis(i,nxt[0][i])&&H[(*it).id]0][i]])nxt[1][i]=nxt[0][i],nxt[0][i]=(*it).id;
else if(dis(i,(*it).id)1][i]))nxt[1][i]=(*it).id;
else if(dis(i,(*it).id)==dis(i,nxt[1][i])&&H[(*it).id]1][i]])nxt[1][i]=(*it).id;
}
void Init(){//预处理1:处理每个点往后的城市
for(int i=n;i>=1;i--){
Set.insert((node){H[i],i});
it=Set.find((node){H[i],i});
if(it!=Set.end()){//往后
it++;int p=1;
if(it!=Set.end()){solve(i);p=0;}
it++;
if(it!=Set.end()&&p==0)solve(i);
}
it=Set.find((node){H[i],i});
if(it!=Set.begin()){//往前
it--;solve(i);
if(it!=Set.begin()){it--;solve(i);}
}
}
}//这么写有点麻烦,可以有简便的方法
ll da[20][M],db[20][M],fa[20][M];
void Init1(){//预处理2:把倍增处理出来
for(int i=1;i<=n;i++){
fa[0][i]=nxt[0][nxt[1][i]];
if(nxt[1][i]){//往后第二个城市能到达
da[0][i]=dis(i,nxt[1][i]);
if(nxt[0][nxt[1][i]])db[0][i]=dis(nxt[1][i],nxt[0][nxt[1][i]]);//往后一个能到达
}
}
for(int j=1;j<20;j++)
for(int i=1;i<=n;i++){
fa[j][i]=fa[j-1][fa[j-1][i]];
da[j][i]=da[j-1][i]+da[j-1][fa[j-1][i]];
db[j][i]=db[j-1][i]+db[j-1][fa[j-1][i]];
}
}
void Walk(int s1,ll x,ll &Da,ll &Db){
for(int i=19;i>=0;i--)//倍增跳跃
if(da[i][s1]+db[i][s1]+Da+Db<=x)
Da+=da[i][s1],Db+=db[i][s1],s1=fa[i][s1];
if(da[0][s1]+Da+Db<=x)//单独判断小A能否再走一次
Da+=da[0][s1];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&H[i]),A[i]=(node){H[i],i};
Init();Init1();
ll x0;int ans1=1;double mn=1e9;
scanf("%lld",&x0);
for(int i=1;i<=n;i++){
ll Da=0,Db=0;//高度很大,要开long long
Walk(i,x0,Da,Db);
if(Db==0&&mn==1e9){if(H[ans1]else if(mn-1.0*Da/Db>1e-9){ans1=i;mn=1.0*Da/Db;}
else if(mn-1.0*Da/Db<1e-9&&mn-1.0*Da/Db>=0&&H[ans1]printf("%d\n",ans1);
int m;
scanf("%d",&m);
while(m--){
ll x1,Da=0,Db=0;int s1;
scanf("%d%lld",&s1,&x1);
Walk(s1,x1,Da,Db);
printf("%lld %lld\n",Da,Db);
}
return 0;
}
考试的时候第三题暴力里把一个变量名打错了,然后跟上面的变量名重复了,然后就只有5分,改了一下就70了。要是改成函数就不会错了。