Vigenère 密碼
題目描述
16 世紀法國外交家 Blaise de Vigenère 設計了一種多表密碼加密算法――Vigenère 密碼。
Vigenère 密碼的加密解密算法簡單易用,且破譯難度比較高,曾在美國南北戰爭中為南軍所廣泛使用。
在密碼學中,我們稱需要加密的信息為明文,用 M 表示;稱加密后的信息為密文,用C 表示;而密鑰是一種參數,是將明文轉換為密文或將密文轉換為明文的算法中輸入的數據,記為 k。 在 Vigenère 密碼中,密鑰 k 是一個字母串,k=k1k2…kn。當明文 M=m1m2…mn時,得到的密文 C=c1c2…cn,其中 ci=mi?ki,運算?的規則如下表所示:
Vigenère 加密在操作時需要注意:
- ?運算忽略參與運算的字母的大小寫,并保持字母在明文 M 中的大小寫形式;
- 當明文 M 的長度大于密鑰 k 的長度時,將密鑰 k 重復使用。
例如,明文 M=Helloworld,密鑰 k=abc 時,密文 C=Hfnlpyosnd。
輸入輸出格式
輸入格式:
輸入共 2 行。
第一行為一個字符串,表示密鑰 k,長度不超過 100,其中僅包含大小寫字母。第二行
為一個字符串,表示經加密后的密文,長度不超過 1000,其中僅包含大小寫字母。
輸出格式:
輸出共 1 行,一個字符串,表示輸入密鑰和密文所對應的明文。
輸入輸出樣例
輸入樣例#1:
CompleteVictory
Yvqgpxaimmklongnzfwpvxmniytm
輸出樣例#1:
Wherethereisawillthereisaway
思路
比較簡單的暴力模擬
代碼
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int key[100];
char k[100],mw[1000],ans[1000];
int i,lk,lmw;
cin>>k>>mw;
lk=strlen(k);
lmw=strlen(mw);
for(i=0;i<lk;i++)
{
if(k[i]>='a')key[i]=k[i]-'a';
else key[i]=k[i]-'A';
}
i=0;
while(i<lmw)
{
ans[i]=mw[i]-key[i%lk];
if((ans[i]<'A')||(ans[i]<'a'&&mw[i]>='a'))ans[i]=ans[i]+26;
cout<<ans[i];
i++;
}
return 0;
}
國王游戲
題目描述
恰逢 H 國國慶,國王邀請 n 位大臣來玩一個有獎游戲。首先,他讓每個大臣在左、右手上面分別寫下一個整數,國王自己也在左、右手上各寫一個整數。然后,讓這 n 位大臣排成一排,國王站在隊伍的最前面。排好隊后,所有的大臣都會獲得國王獎賞的若干金幣,每位大臣獲得的金幣數分別是:排在該大臣前面的所有人的左手上的數的乘積除以他自己右手上的數,然后向下取整得到的結果。
國王不希望某一個大臣獲得特別多的獎賞,所以他想請你幫他重新安排一下隊伍的順序,使得獲得獎賞最多的大臣,所獲獎賞盡可能的少。注意,國王的位置始終在隊伍的最前面。
輸入輸出格式
輸入格式:
第一行包含一個整數 n,表示大臣的人數。
第二行包含兩個整數 a和 b,之間用一個空格隔開,分別表示國王左手和右手上的整數。
接下來 n 行,每行包含兩個整數 a 和 b,之間用一個空格隔開,分別表示每個大臣左手和右手上的整數。
輸出格式:
輸出只有一行,包含一個整數,表示重新排列后的隊伍中獲獎賞最多的大臣所獲得的金幣數。
輸入輸出樣例
輸入樣例#1:
3
1 1
2 3
7 4
4 6
輸出樣例#1:
2
說明
【輸入輸出樣例說明】
- 按 1、2、3 號大臣這樣排列隊伍,獲得獎賞最多的大臣所獲得金幣數為 2;
- 按 1、3、2 這樣排列隊伍,獲得獎賞最多的大臣所獲得金幣數為 2;
- 按 2、1、3 這樣排列隊伍,獲得獎賞最多的大臣所獲得金幣數為 2;
- 按 2、3、1 這樣排列隊伍,獲得獎賞最多的大臣所獲得金幣數為 9;
- 按 3、1、2 這樣排列隊伍,獲得獎賞最多的大臣所獲得金幣數為 2;
- 按 3、2、1 這樣排列隊伍,獲得獎賞最多的大臣所獲得金幣數為 9。
因此,獎賞最多的大臣最少獲得 2 個金幣,答案輸出 2。
【數據范圍】
對于 20%的數據,有 1≤ n≤ 10,0 < a、b < 8;
對于 40%的數據,有 1≤ n≤20,0 < a、b < 8;
對于 60%的數據,有 1≤ n≤100;
對于 60%的數據,保證答案不超過 10^9;
對于 100%的數據,有 1 ≤ n ≤1,000,0 < a、b < 10000。
思路
看到這道題的使得獲得獎賞最多的大臣,所獲獎賞盡可能的少
可以這樣思考,相鄰兩個的人交換對于前面的人答案沒影響、,而且對于后面的人答案也沒有影響。也就是說相鄰兩人的位置交換只會對這兩個人產生影響。那么我們就先考慮這兩個人。
設這兩個人分別為i和i+1。左手數字為a[i]和a[i+1],右手數字為b[i]和b[i+1]。兩人的金幣數為w[i]和w[i+1]。
記P[i]=a[1]*a[2]*a[3]*...*a[i]
可得:
w[i]=P[i-1]/b[i];
w[i+1]=P[i]/b[i+1];
又P[i]=P[i-1]*a[i]
那么 w[i+1]=P[i-1]*a[i]/b[i+1]=w[i]*a[i]*b[i]/b[i+1]
不難看出,在這個相鄰的二元組中,前面的數不受后面的影響,而后面的金幣數決定于w[i],a[i],b[i]。
推廣到整個排隊方案,前面的金幣數和a[i],b[i]都會影響后面的答案。貪心原則便出來了:按a[i]*b[i]為關鍵字從小到大排序,相同的順序無所謂。最后再掃一遍,算出答案即可。
注意一點:乘除法都要寫高精度,答案有10000多位。
代碼
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
int N;
int a[1005],b[1005],ka,kb;
int ans[20000],t[20000],lena,lent,tt[20000],t2[20000],len;
void _qst_ab(int l,int r)
{
int i=l,j=r,ma=a[(i+j)>>1],mb=b[(i+j)>>1];
while(i<=j)
{
while(a[i]*b[i]<ma*mb) i++;
while(a[j]*b[j]>ma*mb) j--;
if(i<=j)
{
swap(a[i],a[j]);
swap(b[i],b[j]);
i++;j--;
}
}
if(l<j) _qst_ab(l,j);
if(i<r) _qst_ab(i,r);
}
void _init()
{
scanf("%d%d%d",&N,&a[0],&b[0]);
for(int i=1;i<=N;i++)
scanf("%d%d",&a[i],&b[i]);
}
void _get_t(int Left,int Right)
{
for(int i=1;i<=lent;i++)
{
tt[i]+=t[i]*Left;
tt[i+1]+=tt[i]/10;
tt[i]%=10;
}
lent++;
while(tt[lent]>=10)
{
tt[lent+1]=tt[lent]/10;
tt[lent]%=10;
lent++;
}
while(lent>1&&tt[lent]==0) lent--;
len=lent;
memcpy(t,tt,sizeof(tt));
memset(tt,0,sizeof(tt));
for(int i=1,j=len;i<=len;i++,j--)
t2[i]=t[j];
int x=0,y=0;
for(int i=1;i<=len;i++)
{
y=x*10+t2[i];
tt[i]=y/Right;
x=y%Right;
}
x=1;
while(x<len&&tt[x]==0) x++;
memset(t2,0,sizeof(t2));
for(int i=1,j=x;j<=len;i++,j++)
t2[i]=tt[j];
memcpy(tt,t2,sizeof(t2));
len=len+1-x;
}
bool _cmp()
{
if(len>lena) return true;
if(len<lena) return false;
for(int i=1;i<=len;i++)
{
if(ans[i]<tt[i]) return true;
if(ans[i]>tt[i]) return false;
}
return false;
}
void _solve()
{
_qst_ab(1,N);
t[1]=1;lent=1;
for(int i=1;i<=N;i++)
{
memset(tt,0,sizeof(tt));
len=0;
_get_t(a[i-1],b[i]);
if(_cmp())
{
memcpy(ans,tt,sizeof(tt));
lena=len;
}
}
for(int i=1;i<=lena;i++)
printf("%d",ans[i]);
printf("\n");
}
int main()
{
_init();
_solve();
return 0;
}
借教室
題目描述
在大學期間,經常需要租借教室。大到院系舉辦活動,小到學習小組自習討論,都需要向學校申請借教室。教室的大小功能不同,借教室人的身份不同,借教室的手續也不一樣。
面對海量租借教室的信息,我們自然希望編程解決這個問題。
我們需要處理接下來n天的借教室信息,其中第i天學校有ri個教室可供租借。共有m份訂單,每份訂單用三個正整數描述,分別為dj,sj,tj,表示某租借者需要從第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj個教室。
我們假定,租借者對教室的大小、地點沒有要求。即對于每份訂單,我們只需要每天提
供dj個教室,而它們具體是哪些教室,每天是否是相同的教室則不用考慮。
借教室的原則是先到先得,也就是說我們要按照訂單的先后順序依次為每份訂單分配教室。如果在分配的過程中遇到一份訂單無法完全滿足,則需要停止教室的分配,通知當前申請人修改訂單。這里的無法滿足指從第sj天到第tj天中有至少一天剩余的教室數量不足dj個。
現在我們需要知道,是否會有訂單無法完全滿足。如果有,需要通知哪一個申請人修改訂單。
輸入輸出格式
輸入格式:
第一行包含兩個正整數n,m,表示天數和訂單的數量。
第二行包含n個正整數,其中第i個數為ri,表示第i天可用于租借的教室數量。
接下來有m行,每行包含三個正整數dj,sj,tj,表示租借的數量,租借開始、結束分別在第幾天。
每行相鄰的兩個數之間均用一個空格隔開。天數與訂單均用從1開始的整數編號。
輸出格式:
如果所有訂單均可滿足,則輸出只有一行,包含一個整數 0。否則(訂單無法完全滿足)
輸出兩行,第一行輸出一個負整數-1,第二行輸出需要修改訂單的申請人編號。
輸入輸出樣例
輸入樣例#1:
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
輸出樣例#1:
-1
2
說明
【輸入輸出樣例說明】
第 1 份訂單滿足后,4 天剩余的教室數分別為 0,3,2,3。第 2 份訂單要求第 2 天到第 4 天每天提供 3 個教室,而第 3 天剩余的教室數為 2,因此無法滿足。分配停止,通知第2 個申請人修改訂單。
【數據范圍】
對于10%的數據,有1≤ n,m≤ 10;
對于30%的數據,有1≤ n,m≤1000;
對于 70%的數據,有1 ≤ n,m ≤ 10^5;
對于 100%的數據,有1 ≤ n,m ≤ 10^6,0 ≤ ri,dj≤ 10^9,1 ≤ sj≤ tj≤ n。
思路
看數據范圍,應該是一個log的算法,發現是每一次都維護一個范圍的數,果斷線段樹……
維護一個區間的所有值的線段樹需要加lazy優化,要不然絕對T掉。
所以總結一句:本題為裸線段樹+lazy優化
代碼
#include <cstdio>
#define MAXN 1100010
using namespace std;
struct rec
{
int sum;
int inc;//lazy
};
int m,n;
rec tree[2*MAXN];
int i;
int u,v,w;
bool finish=false;
inline void read(int &x) //讀入優化,據說不加會T?
{
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
x=0;
while(ch<='9'&&ch>='0')
{
x=x*10+ch-'0';
ch=getchar();
}
return ;
}
int mymin(int a,int b)
{
return a<b?a:b;
}
void Build_A_Tree(int pos,int l,int r)
{
if (l+1==r)
{
int tmp; read(tmp);
tree[pos].sum=tmp;
return ;
}
int mid=(l+r)>>1;
Build_A_Tree(pos<<1,l,mid);
Build_A_Tree((pos<<1)+1,mid,r);
tree[pos].sum=mymin(tree[pos<<1].sum,tree[(pos<<1)+1].sum);
}
inline void update(int pos)
{
if (!tree[pos].inc) return ;
tree[pos<<1].inc+=tree[pos].inc;
tree[(pos<<1)+1].inc+=tree[pos].inc;
tree[pos<<1].sum+=tree[pos].inc;
tree[(pos<<1)+1].sum+=tree[pos].inc;
tree[pos].inc=0;
}
void modify(int pos,int l,int r,int ll,int rr,int delta)
{
if (finish) return ;
if (l>=ll&&r<=rr)
{
tree[pos].sum+=delta;
if (tree[pos].sum<0)
{
printf("-1\n%d\n",i);
finish=true;
return ;
}
tree[pos].inc+=delta;
return ;
}
update(pos);
int mid=(l+r)>>1;
if (ll<mid) modify(pos<<1,l,mid,ll,rr,delta);
if (rr>mid) modify((pos<<1)+1,mid,r,ll,rr,delta);
tree[pos].sum=mymin(tree[pos<<1].sum,tree[(pos<<1)+1].sum);
}
int main()
{
read(n);read(m);
Build_A_Tree(1,1,n+1);
for (i=1;i<=m;i++){
read(u);read(v);read(w);
modify(1,1,n+1,v,w+1,-u);
if (finish) return 0;
}
printf("0\n");
return 0;
}
同余方程
題目描述
求關于 x 的同余方程 ax ≡ 1 (mod b)的最小正整數解。
輸入輸出格式
輸入格式:
輸入只有一行,包含兩個正整數 a, b,用一個空格隔開。
輸出格式:
輸出只有一行,包含一個正整數 x0,即最小正整數解。輸入數據保證一定有解。
輸入輸出樣例
輸入樣例#1:
3 10
輸出樣例#1:
7
說明
【數據范圍】
對于 40%的數據,2 ≤b≤ 1,000;
對于 60%的數據,2 ≤b≤ 50,000,000;
對于 100%的數據,2 ≤a, b≤ 2,000,000,000。
思路
代碼
#include <cstdio>
using namespace std;
int n,a,b,ans=0,x,y;
void exgcd(int a,int b){
if(b==1){y=1,x=0;return;}
exgcd(b,a%b);
int temp=x;x=y;y=temp-x*(a/b);
}
int main(){
scanf("%d%d",&a,&b);
exgcd(a,b);
printf("%d",(x%b+b)%b);
}
開車旅行
題目描述
小 A 和小 B 決定利用假期外出旅行,他們將想去的城市從 1 到 N 編號,且編號較小的城市在編號較大的城市的西邊,已知各個城市的海拔高度互不相同,記城市 i 的海拔高度為Hi,城市 i 和城市 j 之間的距離 d[i,j]恰好是這兩個城市海拔高度之差的絕對值,即d[i,j] = |Hi? Hj|。 旅行過程中,小 A 和小 B 輪流開車,第一天小 A 開車,之后每天輪換一次。他們計劃選擇一個城市 S 作為起點,一直向東行駛,并且最多行駛 X 公里就結束旅行。小 A 和小 B的駕駛風格不同,小 B 總是沿著前進方向選擇一個最近的城市作為目的地,而小 A 總是沿著前進方向選擇第二近的城市作為目的地(注意:本題中如果當前城市到兩個城市的距離相同,則認為離海拔低的那個城市更近)。如果其中任何一人無法按照自己的原則選擇目的城市,或者到達目的地會使行駛的總距離超出 X 公里,他們就會結束旅行。
在啟程之前,小 A 想知道兩個問題:
對于一個給定的 X=X0,從哪一個城市出發,小 A 開車行駛的路程總數與小 B 行駛的路程總數的比值最小(如果小 B 的行駛路程為 0,此時的比值可視為無窮大,且兩個無窮大視為相等)。如果從多個城市出發,小 A 開車行駛的路程總數與小 B 行駛的路程總數的比值都最小,則輸出海拔最高的那個城市。
對任意給定的 X=Xi和出發城市 Si,小 A 開車行駛的路程總數以及小 B 行駛的路程總數。
輸入輸出格式
輸入格式:
- 第一行包含一個整數 N,表示城市的數目。
- 第二行有 N 個整數,每兩個整數之間用一個空格隔開,依次表示城市 1 到城市 N 的海拔高度,即 H1,H2,……,Hn,且每個 Hi都是不同的。
- 第三行包含一個整數 X0。
- 第四行為一個整數 M,表示給定 M 組 Si和 Xi。
接下來的 M 行,每行包含 2 個整數 Si和 Xi,表示從城市 Si出發,最多行駛 Xi公里。
輸出格式:
輸出共 M+1 行。
第一行包含一個整數 S0,表示對于給定的 X0,從編號為 S0的城市出發,小 A 開車行駛的路程總數與小 B 行駛的路程總數的比值最小。
接下來的 M 行,每行包含 2 個整數,之間用一個空格隔開,依次表示在給定的 Si和
Xi下小 A 行駛的里程總數和小 B 行駛的里程總數。
輸入輸出樣例
輸入樣例#1:
4
2 3 1 4
3
4
1 3
2 3
3 3
4 3
輸出樣例#1:
1
1 1
2 0
0 0
0 0
輸入樣例#2:
10
4 5 6 1 2 3 7 8 9 10
7
10
1 7
2 7
3 7
4 7
5 7
6 7
7 7
8 7
9 7
10 7
輸出樣例#2:
2
3 2
2 4
2 1
2 4
5 1
5 1
2 1
2 0
0 0
0 0
說明
【輸入輸出樣例 1 說明】
各個城市的海拔高度以及兩個城市間的距離如上圖所示。
- 如果從城市 1 出發,可以到達的城市為 2,3,4,這幾個城市與城市 1 的距離分別為 1,1,2,但是由于城市 3 的海拔高度低于城市 2,所以我們認為城市 3 離城市 1 最近,城市 2 離城市1 第二近,所以小 A 會走到城市 2。到達城市 2 后,前面可以到達的城市為 3,4,這兩個城市與城市 2 的距離分別為 2,1,所以城市 4 離城市 2 最近,因此小 B 會走到城市 4。到達城市 4 后,前面已沒有可到達的城市,所以旅行結束。
- 如果從城市 2 出發,可以到達的城市為 3,4,這兩個城市與城市 2 的距離分別為 2,1,由于城市 3 離城市 2 第二近,所以小 A 會走到城市 3。到達城市 3 后,前面尚未旅行的城市為4,所以城市 4 離城市 3 最近,但是如果要到達城市 4,則總路程為 2+3=5>3,所以小 B 會直接在城市 3 結束旅行。
- 如果從城市 3 出發,可以到達的城市為 4,由于沒有離城市 3 第二近的城市,因此旅行還未開始就結束了。
- 如果從城市 4 出發,沒有可以到達的城市,因此旅行還未開始就結束了。
【輸入輸出樣例 2 說明】
當 X=7 時, - 如果從城市 1 出發,則路線為 1 -> 2 -> 3 -> 8 -> 9,小 A 走的距離為 1+2=3,小 B 走的距離為 1+1=2。(在城市 1 時,距離小 A 最近的城市是 2 和 6,但是城市 2 的海拔更高,視為與城市 1 第二近的城市,所以小 A 最終選擇城市 2;走到 9 后,小 A 只有城市 10 可以走,沒有第 2 選擇可以選,所以沒法做出選擇,結束旅行)
- 如果從城市 2 出發,則路線為 2 -> 6 -> 7 ,小 A 和小 B 走的距離分別為 2,4。
- 如果從城市 3 出發,則路線為 3 -> 8 -> 9,小 A 和小 B 走的距離分別為 2,1。
- 如果從城市 4 出發,則路線為 4 -> 6 -> 7,小 A 和小 B 走的距離分別為 2,4。
- 如果從城市 5 出發,則路線為 5 -> 7 -> 8 ,小 A 和小 B 走的距離分別為 5,1。
- 如果從城市 6 出發,則路線為 6 -> 8 -> 9,小 A 和小 B 走的距離分別為 5,1。
- 如果從城市 7 出發,則路線為 7 -> 9 -> 10,小 A 和小 B 走的距離分別為 2,1。
- 如果從城市 8 出發,則路線為 8 -> 10,小 A 和小 B 走的距離分別為 2,0。
- 如果從城市 9 出發,則路線為 9,小 A 和小 B 走的距離分別為 0,0(旅行一開始就結束了)。
- 如果從城市 10 出發,則路線為 10,小 A 和小 B 走…
思路
用平衡樹處理出每個點開始小A/小B能到達的下個點,然后加個倍增就行了
第一問枚舉起點即可
代碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<set>
#include<ctime>
#include<vector>
#include<cmath>
#include<algorithm>
#include<map>
#define inf 5000000000LL
#define ll long long
using namespace std;
const double pi=acos(-1.0);
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 bin[20];
int n,x0,m;
int h[100005];
ll a[100005],b[100005],va[100005][17],vb[100005][17];
int fa[100005],fb[100005],to[100005][17];
set<ll> q;
map<ll,int> mp;
struct data{ll h,key;}t[5];
inline bool operator<(data a,data b)
{
return a.key<b.key||(a.key==b.key&&a.h<b.h);
}
void pre()
{
for(int i=n;i;i--)
{
q.insert(h[i]);
t[1].h=*--q.lower_bound(h[i]),t[2].h=*q.upper_bound(h[i]);
if(t[1].h!=-inf)t[3].h=*--q.lower_bound(t[1].h);
else t[3].h=-inf;
if(t[2].h!=inf)t[4].h=*q.upper_bound(t[2].h);
else t[4].h=inf;
for(int k=1;k<=4;k++)
t[k].key=abs(t[k].h-h[i]);
sort(t+1,t+5);
a[i]=t[2].key;fa[i]=mp[t[2].h];
b[i]=t[1].key;fb[i]=mp[t[1].h];
for(int j=0;j<=16;j++)
if(j==0)
{
if(fa[i]){va[i][0]=a[i];to[i][0]=fa[i];}
}
else if(j==1)
{
if(fb[fa[i]]){va[i][1]=a[i];vb[i][1]=b[fa[i]];to[i][1]=fb[fa[i]];}
}
else if(to[to[i][j-1]][j-1])
{
va[i][j]=va[i][j-1]+va[to[i][j-1]][j-1];
vb[i][j]=vb[i][j-1]+vb[to[i][j-1]][j-1];
to[i][j]=to[to[i][j-1]][j-1];
}
else break;
}
}
double cal1(int x,int val)
{
int t1=0,t2=0;
for(int i=16;i>=0;i--)
if(to[x][i]&&t1+va[x][i]+t2+vb[x][i]<=val)
{
t1+=va[x][i];t2+=vb[x][i];
x=to[x][i];
}
if(t2==0)return inf;
return (double)t1/(double)t2;
}
void cal2(int x,int val)
{
int t1=0,t2=0;
for(int i=16;i>=0;i--)
if(to[x][i]&&t1+va[x][i]+t2+vb[x][i]<=val)
{
t1+=va[x][i];t2+=vb[x][i];
x=to[x][i];
}
printf("%d %d\n",t1,t2);
}
void solve1()
{
double mn=1e60;int ans;
x0=read();
for(int i=1;i<=n;i++)
{
double t=cal1(i,x0);
if(t<mn||(t==mn&&h[i]>h[ans]))
{
mn=t;ans=i;
}
}
printf("%d\n",ans);
}
void solve2()
{
m=read();
for(int i=1;i<=m;i++)
{
int s=read(),x=read();
cal2(s,x);
}
}
int main()
{
n=read();
q.insert(-inf);q.insert(inf);
for(int i=1;i<=n;i++)
{
h[i]=read();
mp[h[i]]=i;
}
pre();
solve1();
solve2();
return 0;
}