神奇的幻方
題目描述
幻方是一種很神奇的NN矩陣:它由數字1,2,3,……,NN構成,且每行、每列及兩條對角線上的數字之和都相同。
當N為奇數時,我們可以通過以下方法構建一個幻方:
首先將1寫在第一行的中間。
之后,按如下方式從小到大依次填寫每個數K(K=2,3,…,N*N):
- 若(K?1)在第一行但不在最后一列,則將K填在最后一行,(K?1)所在列的右一列;
- 若(K?1)在最后一列但不在第一行,則將K填在第一列,(K?1)所在行的上一行;
- 若(K?1)在第一行最后一列,則將K填在(K?1)的正下方;
- 若(K?1)既不在第一行,也不在最后一列,如果(K?1)的右上方還未填數,則將K填在(K?1)的右上方,否則將K填在(K?1)的正下方。
現給定N請按上述方法構造N*N的幻方。
輸入輸出格式
輸入格式:
輸入文件只有一行,包含一個整數N即幻方的大小。
輸出格式:
輸出文件包含N行,每行N個整數,即按上述方法構造出的N*N的幻方。相鄰兩個整數之間用單個空格隔開。
輸入輸出樣例
輸入樣例#1:
3
輸出樣例#1:
8 1 6
3 5 7
4 9 2
思路
無難度模擬
代碼
#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
int n,x,y;
int mp[200][200];
int main()
{
scanf("%d",&n);
x = 1,y = (n+1)/2;
mp[x][y]=1;
for(int i=2;i<=n*n;i++)
{
if(x==1&&y!=n)
x=n,y++;
else if(x!=1&&y==n)
x--,y=1;
else if(x==1&&y==n)
x++;
else if(!mp[x-1][y+1])
x--,y++;
else
x++;
mp[x][y]=i;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
printf("%d ",mp[i][j]);
printf("\n");
}
}
信息傳遞
題目描述
有n個同學(編號為1到n)正在玩一個信息傳遞的游戲。在游戲里每人都有一個固定的信息傳遞對象,其中,編號為i的同學的信息傳遞對象是編號為Ti同學。
游戲開始時,每人都只知道自己的生日。之后每一輪中,所有人會同時將自己當前所知的生日信息告訴各自的信息傳遞對象(注意:可能有人可以從若干人那里獲取信息,但是每人只會把信息告訴一個人,即自己的信息傳遞對象)。當有人從別人口中得知自己的生日時,游戲結束。請問該游戲一共可以進行幾輪?
輸入輸出格式
輸入格式:
輸入共2行。
第1行包含1個正整數n表示n個人。
第2行包含n個用空格隔開的正整數T1,T2,……,Tn其中第i個整數Ti示編號為i
的同學的信息傳遞對象是編號為Ti的同學,Ti≤n且Ti≠i
數據保證游戲一定會結束。
輸出格式:
輸出共 1 行,包含 1 個整數,表示游戲一共可以進行多少輪。
輸入輸出樣例
輸入樣例#1:52 4 2 3 1
輸出樣例#1:3
說明
樣例1解釋
游戲的流程如圖所示。當進行完第 3 輪游戲后, 4 號玩家會聽到 2 號玩家告訴他自
己的生日,所以答案為 3。當然,第 3 輪游戲后, 2 號玩家、 3 號玩家都能從自己的消息
來源得知自己的生日,同樣符合游戲結束的條件。
對于 30%的數據, n ≤ 200;
對于 60%的數據, n ≤ 2500;
對于 100%的數據, n ≤ 200000。
思路
找最小環
先把不可能的人踢了
即:先把入度為0的點刪除,然后把這個點指向的點的入度-1,如果入度為0,也刪去,這樣就只保留有用的點,那么從任意一個點開始,用vis數組記錄是否被訪問過,訪問到一個新節點就累加計數器,然后就做出來了.bfs和dfs都可以
代碼
#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
int to[200010],vis[200010], rubian[200010];
int n,ans;
queue <int> q;
int main()
{
memset(to, 0, sizeof(to));
memset(vis, 0, sizeof(vis));
memset(rubian, 0, sizeof(rubian));
scanf("%d", &n);
ans = n;
for (int i = 1; i <= n; i++)
{
scanf("%d", &to[i]);
++rubian[to[i]];
}
for (int i = 1; i <= n; i++)
if (rubian[i] == 0)
{
q.push(i);
vis[i] = 1;
}
while (!q.empty())
{
int u = q.front();
q.pop();
--rubian[to[u]];
if (rubian[to[u]] == 0)
{
vis[to[u]] = 1;
q.push(to[u]);
}
}
int temp,j;
for (int i = 1; i <= n; i++)
{
if (vis[i] == 0 && rubian[i] != 0)
{
vis[i] = 1;
temp = 1;
j = to[i];
while (!vis[j])
{
vis[j] = 1;
j = to[j];
temp++;
}
if (temp <= ans)
ans = temp;
}
}
printf("%d\n", ans);
return 0;
}
跳石頭
題目描述
這項比賽將在一條筆直的河道中進行,河道中分布著一些巨大巖石。組委會已經選擇好了兩塊巖石作為比賽起點和終點。在起點和終點之間,有 N 塊巖石(不含起點和終 點的巖石)。在比賽過程中,選手們將從起點出發,每一步跳向相鄰的巖石,直至到達 終點。
為了提高比賽難度,組委會計劃移走一些巖石,使得選手們在比賽過程中的最短跳 躍距離盡可能長。由于預算限制,組委會至多從起點和終點之間移走 M 塊巖石(不能 移走起點和終點的巖石)。
輸入輸出格式
輸入格式:
輸入文件名為 stone.in。
輸入文件第一行包含三個整數 L,N,M,分別表示起點到終點的距離,起點和終 點之間的巖石數,以及組委會至多移走的巖石數。
接下來 N 行,每行一個整數,第 i 行的整數 Di(0 < Di < L)表示第 i 塊巖石與 起點的距離。這些巖石按與起點距離從小到大的順序給出,且不會有兩個巖石出現在同 一個位置。
輸出格式:
輸出文件名為 stone.out。 輸出文件只包含一個整數,即最短跳躍距離的最大值。
輸入輸出樣例
輸入樣例#1:
25 5 2
2
11
14
17
21
輸出樣例#1:
4
思路
我們對于一個長度x,想看看它是否可以符合刪除石頭數小于等于m,可以這樣做:
從位置的小到大掃遍所有石頭,用一個變量存儲上一個跳到的點。第一個與這上一個點的距離大于等于x的石頭即是下一個跳到的點。這里用了一點貪心的思想:因為如果不跳到第一個符合條件的點上,那么整個隊列的稀疏度就會提高,最終需要刪除的石頭也會更多。因為我們要取最優狀態,所以要保證跳過的石頭數最少。當然,如果某個石頭到終點的距離小于x,那它不能被統計到——所以得刪去后面這些無法跳到的石頭。我自認為這應該也是一個坑點(雖然我第一遍就判斷了)。
這樣,便求出了這個x是否可行,如果可行,那就往右邊二分,但要記得范圍要包括x;若不行,則往左邊二分,右限制不包括x。然后,二分到左右邊界相等,輸出即可。
然后此題就做完了。大家應該很容易理解。
代碼
#include<cstdio>
#include<algorithm>
using namespace std;
int sto[100000];//開大一點,保險
int main()
{
int s,n,m;
scanf("%d%d%d",&s,&n,&m);
int zuo=1,you=s,mid;//所有邊界為1、s
for(int i=0;i<n;i++)scanf("%d",&sto[i]);
sort(sto,sto+n);//從小到大排序
int sg,cnt,ii;
while(zuo!=you)
{
mid=(zuo+you+1)>>1;//位運算加速
sg=cnt=0;//初始化
for(ii=0;ii<n;ii++)
{
if(s-sto[ii]<mid)break;//如解析中所述,若再跳x已超過終點,則不可取此點,它后面的也顯然不可取
if(sto[ii]-sg<mid)cnt++;//跳過
else sg=sto[ii];//貪心,直接跳到
}
cnt+=n-ii;//統計最后被刪除的點數
if(cnt<=m)zuo=mid;
else you=mid-1;//二分邊界更新,具體請見解析
}
printf("%d",zuo);//輸出
return 0;
}
子串
題目描述
有兩個僅包含小寫英文字母的字符串 A 和 B。現在要從字符串 A 中取出 k 個互不重疊的非空子串,然后把這 k 個子串按照其在字符串 A 中出現的順序依次連接起來得到一 個新的字符串,請問有多少種方案可以使得這個新串與字符串 B 相等?注意:子串取出 的位置不同也認為是不同的方案。
輸入輸出格式
輸入格式:
輸入文件名為 substring.in。
第一行是三個正整數 n,m,k,分別表示字符串 A 的長度,字符串 B 的長度,以及問
題描述中所提到的 k,每兩個整數之間用一個空格隔開。 第二行包含一個長度為 n 的字符串,表示字符串 A。 第三行包含一個長度為 m 的字符串,表示字符串 B。
輸出格式:
輸出文件名為 substring.out。 輸出共一行,包含一個整數,表示所求方案數。由于答案可能很大,所以這里要求[b]輸出答案對 1,000,000,007 取模的結果。[/b]
輸入輸出樣例
輸入樣例#1:
6 3 1
aabaab
aab
輸出樣例#1:
2
輸入樣例#2:
6 3 2
aabaab
aab
輸出樣例#2:
7
輸入樣例#3:
6 3 3
aabaab
aab
輸出樣例#3:
7
說明
對于第 1 組數據:1≤n≤500,1≤m≤50,k=1;
對于第 2 組至第 3 組數據:1≤n≤500,1≤m≤50,k=2;
對于第 4 組至第 5 組數據:1≤n≤500,1≤m≤50,k=m;
對于第 1 組至第 7 組數據:1≤n≤500,1≤m≤50,1≤k≤m;
對于第 1 組至第 9 組數據:1≤n≤1000,1≤m≤100,1≤k≤m;
對于所有 10 組數據:1≤n≤1000,1≤m≤200,1≤k≤m。
思路
最后數組壓到二維的就可以省空間了。
代碼
//不壓維
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1000 + 5, maxm = 200 + 5, moder = 1000000007;
int m, n, K;
char stra[maxn], strb[maxm];
int f[maxn][maxm][maxm], s[maxn][maxm][maxm];
void solve() {
s[0][0][0] = 1;
for(int i = 1; i <= n; ++i) {
s[i][0][0] = 1; //注意如果不壓到二維,這是需要的
for(int j = 1; j <= m; ++j)
if(stra[i-1] == strb[j-1]) {
for(int k = 1; k <= min(K, j); ++k) {
f[i][j][k] = (s[i-1][j-1][k-1] + f[i-1][j-1][k]) % moder,
s[i][j][k] = (s[i-1][j][k] + f[i][j][k]) % moder;
}
}else for (int k = 1; k <= min(K, j); ++k) s[i][j][k] = s[i-1][j][k]; //注意如果不壓到二維,這是需要的
}
printf("%d\n", s[n][m][K]);
}
int main() {
scanf("%d%d%d%s%s", &n, &m, &K, stra, strb);
solve();
return 0;
}
//壓維
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1000 + 5, maxm = 200 + 5, moder = 1000000007;
int m, n, K;
char stra[maxn], strb[maxm];
int f[maxm][maxm], s[maxm][maxm];
void solve() {
s[0][0] = 1;
for(int i = 1; i <= n; ++i) {
for(int j = m; j > 0; --j)
if(stra[i-1] == strb[j-1]) {
for(int k = min(K, j); k > 0; --k) {
f[j][k] = (s[j-1][k-1] + f[j-1][k]) % moder,
s[j][k] = (s[j][k] + f[j][k]) % moder;
}
}else fill(f[j], f[j] + min(K, j) + 1, 0);
}
printf("%d\n", s[m][K]);
}
int main() {
scanf("%d%d%d%s%s", &n, &m, &K, stra, strb);
solve();
return 0;
}
斗地主
題目描述
牛牛最近迷上了一種叫斗地主的撲克游戲。斗地主是一種使用黑桃、紅心、梅花、方片的A到K加上大小王的共54張牌來進行的撲克牌游戲。在斗地主中,牌的大小關系根據牌的數碼表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不對牌的大小產生影響。每一局游戲中,一副手牌由n張牌組成。游戲者每次可以根據規定的牌型進行出牌,首先打光自己的手牌一方取得游戲的勝利。
現在,牛牛只想知道,對于自己的若干組手牌,分別最少需要多少次出牌可以將它們打光。請你幫他解決這個問題。
需要注意的是,本題中游戲者每次可以出手的牌型與一般的斗地主相似而略有不同。
具體規則如下:
輸入輸出格式
輸入格式:
第一行包含用空格隔開的2個正整數T和n,表示手牌的組數以及每組手牌的張數。
接下來T組數據,每組數據n行,每行一個非負整數對aibi表示一張牌,其中ai示牌的數碼,bi表示牌的花色,中間用空格隔開。特別的,我們用1來表示數碼A,11表示數碼J,12表示數碼Q,13表示數碼K;黑桃、紅心、梅花、方片分別用1-4來表示;小王的表示方法為01,大王的表示方法為02。
輸出格式:
共T行,每行一個整數,表示打光第i手牌的最少次數。
輸入輸出樣例
輸入樣例#1:
1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1
輸出樣例#1:
3
思路
搜索+剪枝
忽略花色,統計每種碼數出現次數方便出牌。
每次都先出順子,對于手中剩下的牌我們貪心地將剩下的組合牌需要打的次數計算出來,然后更新ans以剪枝。
雙王算作對牌。順子不包括2和雙王。
代碼
#include<cstdio>
#include<cstring>
#define FOR(a,b,c) for(int a=(b);a<=(c);a++)
using namespace std;
const int N = 25;
int a[N],c[N];
int n,T,ans;
int Qans() {
memset(c,0,sizeof(c));
FOR(i,0,13) c[a[i]]++;
int tot=0; //tot帶牌
while(c[4]&&c[2]>1) c[4]--,c[2]-=2,tot++;
while(c[4]&&c[1]>1) c[4]--,c[1]-=2,tot++;
while(c[4]&&c[2]) c[4]--,c[2]--,tot++;
while(c[3]&&c[2]) c[3]--,c[2]--,tot++;
while(c[3]&&c[1]) c[3]--,c[1]--,tot++;
return tot+c[1]+c[2]+c[3]+c[4]; //帶牌+三張 對子 單張
}
void dfs(int now) {
if(now>=ans) return ;
int tmp=Qans();
if(now+tmp<ans) ans=now+tmp;
FOR(i,2,13) { //三順子
int j=i;
while(a[j]>=3) j++;
if(j-i>=2) {
FOR(j2,i+1,j-1) {
FOR(k,i,j2) a[k]-=3;
dfs(now+1);
FOR(k,i,j2) a[k]+=3;
}
}
}
FOR(i,2,13) { //雙順子
int j=i;
while(a[j]>=2) j++;
if(j-i>=3) {
FOR(j2,i+2,j-1) {
FOR(k,i,j2) a[k]-=2;
dfs(now+1);
FOR(k,i,j2) a[k]+=2;
}
}
}
FOR(i,2,13) { //單順子
int j=i;
while(a[j]>=1) j++;
if(j-i>=5) {
FOR(j2,i+4,j-1) {
FOR(k,i,j2) a[k]--;
dfs(now+1);
FOR(k,i,j2) a[k]++;
}
}
}
}
int main() {
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout);
scanf("%d%d",&T,&n);
while(T--) {
memset(a,0,sizeof(a));
int x,y;
FOR(i,1,n) {
scanf("%d%d",&x,&y);
if(x==1) x=13; else if(x) x--;
a[x]++;
}
ans=1e9;
dfs(0);
printf("%d\n",ans);
}
return 0;
}