前言
由於自己的不認真丟了不少分……
NO.1 數列
題目描述
下面數列的第 \(n\) 項:
\(f(0)=a_0,f(1)=a_1,f(2)=a_2\)
\(f(n)=b\times f(n−1)+c\times f(n−2)+d×f(n−3)+e(3\ge n)\)
輸入格式
包含 \(1\) 行,共 \(8\) 個整數:\(a_0、a_1、a_2、b、c、d、e、n\)。
輸出格式
輸出 \(f(n)\) 的後 \(18\) 位(後 \(18\) 位的字首 \(0\) 需要輸出,不足 \(18\) 位用 \(0\) 補齊)。
樣例輸入
1 2 3 4 5 6 7 3
樣例輸出
000000000000000035
資料範圍
對於 \(30\%\) 的資料,\(0\le a_0,a_1,a_2,b,c,d,e,n\le 10^6\)
對於 \(100\%\) 的資料,\(0\le a_0,a_1,a_2,b,c,d,e,n\le 10^{18}\)
分析
看到這樣的類似於菲波那契的一個遞推,很容易就能想到是矩陣快速冪(當時想到了,但是不會寫……),分析一下,每一次乘以一個矩陣就能得到\(f_i\)下一個\(f_{i+1}\)的大小,輸入的時候一直到\(a_2\),所以我們可以得到要求出來\(f_n\),那麼只需要讓矩陣乘\(n-2\)次方,然後再對應相應的位置乘以一個\(a_0,a_1,a_2\)即可。
也就是這樣的矩陣:
和
這個題真是毒瘤……不光矩陣快速冪,還有高精度……高精度只需要計算18位即可,因為最後只是讓輸出18位,然後放程式碼。
程式碼
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 1e18;//取模防止爆long long
ll a0,a1,a2,b,c,d,e,n;
ll gj18(ll x,ll y){//求最後18位高精乘
int a[25]={0};
int b[25]={0};
int c[25]={0};
while(x || a[0]==0){
a[++a[0]] = x%10;
x/=10;
}
while(y || b[0]==0){
b[++b[0]] = y%10;
y/=10;
}
for (int i = 1; i <= a[0]; ++i) {
for (int j = 1; j <= b[0]; ++j) {
if (i + j - 1 <= 18) {
c[i + j - 1] += a[i] * b[j];
c[i + j] += c[i + j - 1] / 10;
c[i + j - 1] %= 10;
}
}
}
ll ret = 0;
for (int i = 18; i; --i) {
ret = ret * 10 + c[i];
}
return ret;
}
struct jz{//矩陣結構題
ll a[5][5];
void First(){//初始化答案矩陣
memset(a,0,sizeof(a));
for(int i=1;i<=4;++i){
a[i][i] = 1;
}
}
void Init(){//初始化開始的需要乘方的矩陣
memset(a,0,sizeof(a));
a[1][1] = b;
a[1][2] = c;
a[1][3] = d;
a[1][4] = e;
a[2][1] = 1;
a[3][2] = 1;
a[4][4] = 1;
}
void cheng(jz &A){//矩陣乘法
jz C;
memset(C.a,0,sizeof(C.a));
for(int i=1;i<=4;++i){
for(int j=1;j<=4;++j){
for(int k=1;k<=4;++k){
C.a[i][j] = C.a[i][j] + gj18(a[i][k],A.a[k][j]);
if(C.a[i][j]>mod)C.a[i][j]-=mod;
}
}
}
memcpy(a,C.a,sizeof(a));
}
};
jz jzqpow(ll a){//矩陣快速冪
jz ret,base;
ret.First();
base.Init();
while(a){
if(a&1)ret.cheng(base);
base.cheng(base);
a>>=1;
}
return ret;
}
void Print(ll x){//輸出後18位
ll a[20];
for(int i=0;i<18;++i){
a[i] = x%10;
x/=10;
}
for(int i=17;i>=0;--i){
printf("%lld",a[i]);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>a0>>a1>>a2>>b>>c>>d>>e>>n;
jz JZ;
if(n>=3){//大於三才行
JZ=jzqpow(n-2);
}
ll ans = gj18(JZ.a[1][1],a2)+gj18(JZ.a[1][2],a1)+gj18(JZ.a[1][3],a0)+JZ.a[1][4];//因為開始的答案矩陣為四個位置的1,所以最後都需要乘以遞推式子裡的a2,a1,a0。
Print(ans);//輸出
return 0;
}
NO.2 旗木雙翼
題目描述
菲菲和牛牛在一塊\(n\)行\(m\)列的棋盤上下棋,菲菲執黑棋先手,牛牛執白棋後手。
棋局開始時,棋盤上沒有任何棋子,兩人輪流在格子上落子,直到填滿棋盤時結束。落子的規則是:一個格子可以落子當且僅當這個格子內沒有棋子且這個格子的左側及上方的所有格子內都有棋子。
\(Itachi\)聽說有不少學弟在省選現場\(AC\)了\(D1T1\),解決了菲菲和牛牛的問題,但是\(Itachi\)聽說有的人認為複雜度玄學,\(Itachi\)並不想難為學弟學妹,他想為大家節約時間做剩下的題,所以將簡化版的\(D1T1\)帶給大家。
\(Itachi\)也在一塊\(n\)行\(m\)列的棋盤上下棋,不幸的是\(Itachi\)只有黑棋,不過幸好只有他一個人玩。現在,\(Itachi\)想知道,一共有多少種可能的棋局(不考慮落子順序,只考慮棋子位置)。
\(Itachi\)也不會為難學弟學妹們去寫高精度,所以只需要告訴\(Itachi\)答案\(mod\) \(998244353\)(一個質數)的結果。
輸入格式
第一行包括兩個整數\(n\),\(m\)表示棋盤為\(n\)行\(m\)列。
輸出格式
一個整數表示可能的棋局種數。
樣例
樣例輸入#1
1 1
樣例輸出#1
2
樣例輸入#2
2 3
樣例輸出#2
10
樣例輸入#3
10 10
樣例輸出#3
184756
資料範圍與提示
對於 \(20\%\)的資料\(n,m\le 10\)
對於 \(30\%\)的資料\(n,m\le 20\)
另有 \(20\%\)的資料\(n\le 5\)
另有 \(20\%\)的資料\(m\le 5\)
對於\(100\%\)的資料\(n,m\le 100000\)
分析
首先看到這個矩陣,其實就是題目給出的矩陣,其中\(n\)行\(m\)列的格子填的數字就是\(n\)行\(m\)列的矩陣的最多方案數。把它歪過來看,讓最小的\(2\)為一個頂點,我們可以發現如果在每個的外邊加一個\(1\),這不就是楊輝三角嗎!根據這個我們就可以知道\(n\)行\(m\)列的數字應該就是一個組合數,即\(C_{m+n}^m\)或者\(C_{m+n}^n\)(其實都一樣),把這個組合數化簡開,就是:
因為最後需要\(mod\ 998244353\),而這個數又剛剛好是一個質數,所以我們就可以求出來\((m+n)!\)然後求一下下邊需要除的那個階乘的逆元(因為除法沒辦法取模),然後乘起來。我在處理的時候直接先除以了一個\(m!\),所以求階乘的是從\(m+1\)開始。完事!
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=998244353;
int qpow(int x,int cnt){//快速冪
int ans=1;
while(cnt){
if(cnt & 1) ans=(ans*x)%Mod;
x=(x*x)%Mod; cnt>>=1;
}
return ans;
}
int jc=1,ans=1;
signed main(){
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=m+1;i<=m+n;++i)
jc=(jc*i)%Mod;//求出(m+n)!/n!
for(int i=1;i<=n;++i) ans=(ans*i)%Mod;//求出m!的逆元
ans=qpow(ans,Mod-2);//費馬小定理求逆元
printf("%lld\n",ans*jc%Mod);
return 0;
}
NO.3 烏龜棋
題目描述
小明過生日的時候,爸爸送給他一副烏龜棋當作禮物。
烏龜棋的棋盤是一行\(N\)個格子,每個格子上一個分數(非負整數)。棋盤第\(1\)格是唯一的起點,第\(N\)格是終點,遊戲要求玩家控制一個烏龜棋子從起點出發走到終點。\(1\ 2\ 3\ 4\ 5……N\)
烏龜棋中\(M\)張爬行卡片,分成\(4\)種不同的型別(\(M\)張卡片中不一定包含所有\(4\)種型別的卡片,見樣例),每種型別的卡片上分別標有\(1、2、3、4\)四個數字之一,表示使用這種卡片後,烏龜棋子將向前爬行相應的格子數。遊戲中,玩家每次需要從所有的爬行卡片中選擇一張之前沒有使用過的爬行卡片,控制烏龜棋子前進相應的格子數,每張卡片只能使用一次。
遊戲中,烏龜棋子自動獲得起點格子的分數,並且在後續的爬行中每到達一個格子,就得到該格子相應的分數。玩家最終遊戲得分就是烏龜棋子從起點到終點過程中到過的所有格子的分數總和。
很明顯,用不同的爬行卡片使用順序會使得最終遊戲的得分不同,小明想要找到一種卡片使用順序使得最終遊戲得分最多。
現在,告訴你棋盤上每個格子的分數和所有的爬行卡片,你能告訴小明,他最多能得到多少分嗎?
輸入格式
第\(1\)行\(2\)個正整數\(N\)和\(M\),分別表示棋盤格子數和爬行卡片數。
第\(2\)行\(N\)個非負整數,\(a_1, a_2,……, a_n\),其中\(a_i\)表示棋盤第\(i\)個格子上的分數。
第\(3\)行\(M\)個整數,\(b_1,b_2,……, b_m\),表示\(M\)張爬行卡片上的數字。
輸入資料保證到達終點時剛好用光\(M\)張爬行卡片,即\(N−1=\)所有卡片上的數字之和。
輸出格式
輸出只有\(1\)行,\(1\)個整數,表示小明最多能得到的分數。
樣例
樣例輸入#1
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
樣例輸出1
73
樣例1解釋
小明使用爬行卡片順序為\(1,1,3,1,2\),得到的分數為\(6+10+14+8+18+17=73\)。注意,由於起點是\(1\),所以自動獲得第\(1\)格的分數\(6\)。
樣例輸入#2
13 8
4 96 10 64 55 13 94 53 5 24 89 8 30
1 1 1 1 1 2 4 1
樣例輸出#2
455
資料範圍與提示
對於\(30\%\)的資料有\(1\le N\le 30,1\le M\le 12\)。
對於\(50\%\)的資料有\(1\le N\le 120,1\le M\le 50\),且\(4\)種爬行卡片,每種卡片的張數不會超過\(20\)。
對於\(100\%\)的資料有\(1\le N\le 350,1\le M\le 120\),且\(4\)種爬行卡片,每種卡片的張數不會超過\(40\);\(0\le a_i\le 100,1\le i\le N;1\le b_i\le 4,1\le i\le M\)。輸入資料保證\(N−1=\sum_{i=1}^Mb_i\)。
分析
一個比較簡單的\(dp\)(陣列開小了,結果就掛了……)。因為只有四種卡片,並且保證全部用完,所以我們就可以開四維陣列進行\(dp\),每一維列舉走幾步使用的卡片。即定義\(f[a][b][c][d]\),表示走一步\(a\)張,走兩步\(b\)張……依次類推。在狀態轉移的時候我們只需要從使用上一種當前卡片少用一張轉移而來,最後取最大值,並且加上當前使用了這麼多卡片以後那個位置的價值,定義\(nn[i]\)來儲存也就是\(nn[a+2\times b+3\times c+4\times d+1]\),最後只需要輸出每個卡片全部都用了的值就行,狀態轉移方程如下:
這裡\(jl\)是為了方便下邊取最大值,不要忘了判定減一之後是否小於\(0\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn = 45;
int f[maxn][maxn][maxn][maxn];
const int N = 400;
int nn[N],mm[N];//這裡千萬不要開成maxn,我就是這麼錯的……
int n,m;
int sum[maxn];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&nn[i]);//記錄每個點的值
}
for(int i=1;i<=m;++i){
scanf("%d",&mm[i]);
sum[mm[i]]++;//記錄每一種的卡片有多少個
}
for(int a=0;a<=sum[1];++a){//依次列舉每種卡片個數
for(int b=0;b<=sum[2];++b){
for(int c=0;c<=sum[3];++c){
for(int d=0;d<=sum[4];++d){
int jl1=0,jl2=0,jl3=0,jl4=0;
if(a)jl1 = f[a-1][b][c][d];//狀態轉移
if(b)jl2 = f[a][b-1][c][d];
if(c)jl3 = f[a][b][c-1][d];
if(d)jl4 = f[a][b][c][d-1];
f[a][b][c][d] = max(max(jl1,jl2),max(jl3,jl4))+nn[a+2*b+3*c+4*d+1];//取最大值,加上到達後的那個點的值
}
}
}
}
cout<<f[sum[1]][sum[2]][sum[3]][sum[4]]<<endl;//輸出結果
return 0;
}
NO.4
題目描述
一年一度的假面舞會又開始了,棟棟也興致勃勃的參加了今年的舞會。
今年的面具都是主辦方特別定製的。每個參加舞會的人都可以在入場時選擇一 個自己喜歡的面具。每個面具都有一個編號,主辦方會把此編號告訴拿該面具的人。為了使舞會更有神祕感,主辦方把面具分為\(k (k\ge 3)\)類,並使用特殊的技術將每個面具的編號標在了面具上,只有戴第\(i\) 類面具的人才能看到戴第\(i+1\) 類面具的人的編號,戴第\(k\) 類面具的人能看到戴第\(1\) 類面具的人的編號。 參加舞會的人並不知道有多少類面具,但是棟棟對此卻特別好奇,他想自己算出有多少類面具,於是他開始在人群中收集資訊。 棟棟收集的資訊都是戴第幾號面具的人看到了第幾號面具的編號。如戴第\(2\)號面具的人看到了第\(5\) 號面具的編號。棟棟自己也會看到一些編號,他也會根據自己的面具編號把資訊補充進去。由於並不是每個人都能記住自己所看到的全部編號,因此,棟棟收集的信 息不能保證其完整性。現在請你計算,按照棟棟目前得到的資訊,至多和至少有多少類面具。由於主辦方已經宣告瞭\(k\ge 3\),所以你必須將這條資訊也考慮進去。
輸入格式
第一行包含兩個整數\(n,m\),用一個空格分隔,\(n\) 表示主辦方總共準備了多少個面具,\(m\) 表示棟棟收集了多少條資訊。
接下來\(m\) 行,每行為兩個用空格分開的整數\(a,b\),表示戴第\(a\) 號面具的人看到了第\(b\) 號面具的編號。相同的數對\(a,b\) 在輸入檔案中可能出現多次。
輸出格式
包含兩個數,第一個數為最大可能的面具類數,第二個數為最小可能的面具類數。
如果無法將所有的面具分為至少\(3\) 類,使得這些資訊都滿足,則認為棟棟收集的資訊有錯誤,輸出兩個\(-1\)。
樣例輸出#1
6 5
1 2
2 3
3 4
4 1
3 5
樣例輸出#1
4 4
樣例輸入#2
3 3
1 2
2 1
2 3
樣例輸出#2
-1 -1
資料範圍與提示
\(50\%\)的資料,滿足\(n\le 300, m\le 1000\);
\(100\%\)的資料,滿足\(n\le 100000, m\le 1000000\)。
分析
分析一下題目的意思,也就是兩條性質:
1、所有看到這個點的是一類。
2、這個點看到的所有是一類。
根據這兩個結論,我們就可以將這個圖進行縮點操作,把同類的點先都縮成一個點,然後進行下一步。
現在只剩下了可能不同種類的點,那麼這個圖還是有兩種情況:
1、圖中有鏈,這種比較好分析,因為只要鏈的長度大於\(3\),那麼任意的種類數都是可以滿足的。所以鏈的最大就是鏈的長度,最小就是\(3\)。
2、有環,那麼假如圖中有好多環,那麼種類的最大數一定是環大小的最大公約數,因為如果比最大公約數大,那麼小一點的環是無法構成的,也就不滿足了。而最小數就是最小的一個大於\(3\)的約數。
為了尋找環和鏈,我們在建邊的時候正常的邊賦值\(1\),反過來就是\(-1\)。最後到某個點的\(dis\),也就是距離,就是當前環或者鏈的大小。最後統計答案即可。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
struct Node{
int v,next,val;
}e[maxn<<1];
int n,m;
int ans,flag[maxn];
int Max,Min;
int tot=-1,head[maxn],vis[maxn],dis[maxn];
void Add(int x,int y,int z){//建邊
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
e[tot].val = z;
}
void dfs1(int x){
vis[x] = 1;
for(int i=head[x];~i;i=e[i].next){
int v = e[i].v;
if(!vis[v]){//沒訪問過
dis[v] = dis[x] + e[i].val;
dfs1(v);
}
else{
ans = __gcd(ans,abs(dis[x]+e[i].val-dis[v]));//求出最大公約數
}
}
}
void dfs2(int x){
Max = max(Max,dis[x]);//求出到鏈的最遠端的最大和最小距離,計算鏈長度
Min = min(Min,dis[x]);
vis[x] = 1;
for(int i=head[x];~i;i=e[i].next){
if(!flag[i]){//沒經過這個點
flag[i] = flag[i^1] = 1;//正向邊和反向邊都標記,不讓他回來
int v = e[i].v;
dis[v] = dis[x] + e[i].val;//記錄長度
dfs2(v);
}
}
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
int x,y;
scanf("%d%d",&x,&y);
Add(x,y,1);//正向權值1
Add(y,x,-1);//反向-1
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i){
if(!vis[i]){//沒有訪問過就開始搜
dfs1(i);
}
}
if(ans){
if(ans<3)printf("-1 -1\n");//小於三不成立
else{//成立的情況
int i;
for(i=3;i<=ans;++i){
if(ans%i==0)break;//找到最小的因子
}
printf("%d %d\n",ans,i);
}
return 0;
}
memset(vis,0,sizeof(vis));//清空
for(int i=1;i<=n;++i){
if(!vis[i]){
Max = Min = dis[i] = 0;//初始化開始點
dfs2(i);
ans += Max-Min+1;//最大減最小加一就是鏈的長度
}
}
if(ans>=3){
printf("%d %d\n",ans,3);//種類大於等於3才行
}
else printf("-1 -1\n");//否則輸出-1
return 0;
}