[jzoj]2018.07.15【NOIP普及組】模擬賽D組:解題報告
目錄:
1.馬農
2.馬語翻譯
3.馬球比賽
4.棋盤遊戲
1.馬農
題目描述:
在觀看完戰馬檢閱之後,來自大草原的兩兄弟決心成為超級“馬農”,專門飼養戰馬。
兄弟兩回到草原,將可以養馬的區域,分為 N*N 的單位面積的正方形, 並實地進行考察,歸納出了每個單位面積可以養馬所獲得的收益。接下來就要開始規劃他們各自的馬場了。
首先,兩人的馬場都必須是矩形區域。同時,為了方便兩人互相照應,也為了防止馬匹互相走散,規定兩個馬場的矩形區域相鄰,且只有一個交點。最後,互不認輸的兩人希望兩個馬場的收益相當,這樣才不會影響他們兄弟的感情。
現在,兄弟兩找到你這位設計師,希望你給他們設計馬場,問共有多少種設計方案。
輸入:
第一行一個整數 N,表示整個草原的大小為 N*N。
接下來 N 行,每行 N 個整數 A(i,j),表示第 i 行第 j 列的單位草地的收成。
(注意:收益可能是負數,養馬也不是包賺的,馬匹也可能出現生病死亡等意外。)
輸出:
輸出符合兩人要求的草原分配方案數。
樣例輸入:
3
1 2 3
4 5 6
7 8 9
樣例輸出:
2
資料範圍限制:
40%的資料, N<=10。
100%的資料, N<=50, -1000<A(i,j)<1000。
思路:
窮舉每個矩形的交點,每個交點會把整個區域劃分成四塊,如下圖
(20170702221706705.png)
①區域對應④區域,③區域對應②區域。分別窮舉每個區域裡的矩形,注意矩形的一個頂點一定為交點。然後用雜湊記錄收益值,找出對應區域相等收益的個數。還有一個優化,在程式碼註釋裡面有介紹。
Code:
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<map> #include<algorithm> #define LL long long #define M 2500000 using namespace std; int n,sum[55][55]; int ans,x,tmp,top; int h[M*2+10],st[55*55]; int main() { while(~scanf("%d",&n)) { memset(sum,0,sizeof(sum)); ans=top=0; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { scanf("%d",&x); sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+x; } } for(int i=1;i<n;i++) { for(int j=1;j<n;j++) //這兩個for窮舉了交點 { //cout<<sum[i][j]<<' '; for(int k=1;k<=i;k++) { for(int l=1;l<=j;l++) //這兩個是窮舉區域裡面的矩形 { tmp=sum[i][j]+sum[k-1][l-1]-sum[k-1][j]-sum[i][l-1]+M; st[top++]=tmp; //用雜湊記錄 ,用map超時orz h[tmp]++; } } for(int k=i+1;k<=n;k++) { for(int l=j+1;l<=n;l++) { tmp=sum[i][j]+sum[k][l]-sum[k][j]-sum[i][l]+M; ans+=h[tmp]; } } while(top) //這裡用了一個堆疊記錄雜湊的個數,直接清空對應的 h[st[--top]]=0; //如果直接清空整個陣列,應該是會超時的 for(int k=i+1;k<=n;k++) { for(int l=1;l<=j;l++) { tmp=sum[i][l-1]+sum[k][j]-sum[i][j]-sum[k][l-1]+M; st[top++]=tmp; h[tmp]++; } } for(int k=1;k<=i;k++) { for(int l=j+1;l<=n;l++) { tmp=sum[i][l]+sum[k-1][j]-sum[k-1][l]-sum[i][j]+M; ans+=h[tmp]; } } while(top) h[st[--top]]=0; } //cout<<endl; } printf("%d\n",ans); } }
2.馬語翻譯
題目描述:
隨著馬場的繁榮,出現了越來越多的新馬種。種族之間的溝通不暢嚴重影響了馬場的和諧。這時,科學家發明了馬語翻譯機器人,正好可以解決這一難題。
機器人有 M 種,每種機器人能完成 K 個馬種之間的語言翻譯。問,利用這些機器人,能否實現 1 種群和 N 種群的馬語翻譯。 若可以,找到翻譯過程至少需要用到多少種語言。
輸入:
第一行三個整數 N, K 和 M,分別表示語言數, 每個機器人能翻譯的語言數, 機器人的數量。
接下來 M 行,每行 K 個整數。表示每個機器人可以翻譯的語言編號(編號從 1 到 N)。
輸出:
輸出最少轉換語言的次數。如果無法完成翻譯,輸出-1。
資料範圍限制:
40%的資料 N<=100, 1<=K<=20, M<=40。
100%的資料 1<=N<=100000, 1<=K<=1000, 1<=M<=1000。
思路:
這是一道圖論題目,要求出從語言1到語言n的最短路,因為N比較大,顯然用bfs(或spfa)效率更高,但是關鍵在於怎麼建圖。
對每個機器人建圖?
每兩個機器人如果有共同的語言就連一條邊。
列舉兩個機器人,列舉機器人的語言。
時間複雜度O(KM^2),顯然超時。
對每種語言建圖?
對每一個機器人的所以語言兩兩建圖。
列舉一個機器人,列舉機器人的兩種語言。
時間複雜度O(MK^2),顯然超時。
換個思路,對機器人和語言同時建圖:把機器人變成點,與語言連邊,通往機器人的邊權為1,其餘為零。
那麼最終的結果就是從語言1到語言n所經過的節點數/(div) 2 + 1
Code:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cctype>
#include<cstdio>
#define MAXN 101005
#define MAXE 2000005
using namespace std;
int n,k,m,cnt,q[MAXN],dis[MAXN],lnk[MAXN],son[MAXE],nxt[MAXE];
bool vis[MAXN];
inline int read()
{
int ret = 0,w = 0;
char ch = 0;
while(!isdigit(ch))
{
w |= ch == '-';
ch = getchar();
}
while(isdigit(ch))
{
ret = (ret << 3) + (ret << 1) + (ch ^ 48);
ch = getchar();
}
return w ? -ret : ret;
}
inline void write(int ret)
{
if(ret < 0)
{
putchar('-');
ret = -ret;
}
if(ret > 9)
write(ret / 10);
putchar(ret % 10 + '0');
}
void add(int x,int y)
{
son[++cnt] = y,nxt[cnt] = lnk[x],lnk[x] = cnt;
}
void bfs()
{
int head = 0,tail = 1;
q[1] = 1;
vis[1] = 1;
while (head ^ tail)
{
int x = q[++head];
for (int j = lnk[x];j;j = nxt[j])
if (!vis[son[j]])
{
vis[son[j]] = 1;
dis[son[j]] = dis[x] + 1;
q[++tail] = son[j];
}
}
}
int main()
{
//freopen("trans.in","r",stdin);
//freopen("trans.out","w",stdout);
n = read();
k = read();
m = read();
for (int i = 1;i <= m;i++)
for (int j = 1;j <= k;j++)
{
int x = read();
add(x,i + n);
add(i + n,x);
}
bfs();
if (vis[n])
printf("%d",(dis[n] >> 1) + 1);
else
cout << "-1";
return 0;
}
3.馬球比賽
題目:
在解決了馬語翻譯問題後,馬匹數量越來越多,不少鄉鎮都有了數量可觀的馬匹,開始出現馬球比賽。鄉鎮之間決定進行馬球聯賽。
聯賽的賽制,主要是比賽雙方的馬匹數量,成了一個急需解決的問題。首先,所有鄉鎮都要求,本鄉鎮所有的馬匹都必須參賽,或者都不參賽(若組隊的馬匹數量不是該鎮馬匹數量的約數,將無法參賽)。其次,在本鄉鎮,選出最佳球隊,參加鄉鎮間聯賽。
現在,比賽組織方希望滿足所有參賽鄉鎮的要求,並且使得決賽的馬匹儘可能多,請你設計每個球隊馬匹的數量,使得決賽馬匹數最大。注意,決賽至少有 2 個隊伍晉級。
輸入:
第一行一個整數 N,表示想要報名參賽的鄉鎮。
接下來 N 個用空格分開的整數 a(i),表示第 i 個鄉鎮報名參賽的馬匹數。
輸出:
計算出決賽最大參與的馬匹數。
資料範圍:
20%的資料 2<=N<=100, 1<=a(i)<=10000。
50%的資料 2<=N<=20000。
100%的資料 2<=N<=200000, 1<=a(i)<= 2000000。
思路:
卡常 + 偽DP
Code:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cctype>
#include<cstdio>
#include<cmath>
using namespace std;
inline int read()
{
int ret = 0,w = 0;
char ch = 0;
while(!isdigit(ch))
{
w |= ch == '-';
ch = getchar();
}
while(isdigit(ch))
{
ret = (ret << 3) + (ret << 1) + (ch ^ 48);
ch = getchar();
}
return w ? -ret : ret;
}
inline void write(int ret)
{
if(ret < 0)
{
putchar('-');
ret = -ret;
}
if(ret > 9)
write(ret / 10);
putchar(ret % 10 + '0');
}
long long f[2000010],p[2000010];
int n,x,y,z,ls;
int main()
{
long long s;
n = read();
for(int i = 1;i <= n;i++)
{
x = read();
f[x]++;
if(x > z)
z = x;
}
s = n;
for(int i = 2;i <= z;i++)
if(f[i] > 0)
{
for(int j = 2;j * j <= i;j++)
{
if(p[i] >= 2)
continue;
p[j]++;
if(i % j == 0)
{
f[j] += f[i];
if(j * j != i)
f[i / j] += f[i];
}
}
}
for(int i = 1;i <= z;i++)
{
if (f[i] > 1 && f[i] * i > s)
s = f[i] * i;
}
write(s);
}
4.棋盤遊戲
題目描述:
給定一個N*M的棋盤,每個格子裡最多隻可以放置一個棋子,求有多少种放置方案使得任意2*2的正方形區域內恰有2個棋子。
輸入:
棋盤的長與寬 N M
輸出:
一個整數,代表可行的方案數。
資料範圍:
對於30%的資料 N,M<=20
對於100%的資料 N,M<=10000
思路:
打了表之後發現了規律:2 ^ n + 2 ^ m - 2;
但一看資料範圍就知道直接套公式會炸,所以要用高精度
Code:
# include<cctype>
# include<cstdio>
# include<cstring>
using namespace std;
int n,m;
int sum[5010],t[5010],min_[5010],k;
inline int read()
{
int ret = 0,w = 0;
char ch = 0;
while(!isdigit(ch))
{
w |= ch == '-';
ch = getchar();
}
while(isdigit(ch))
{
ret = (ret << 3) + (ret << 1) + (ch ^ 48);
ch = getchar();
}
return w ? -ret : ret;
}
inline void write(int ret)
{
if(ret < 0)
{
putchar('-');
ret = -ret;
}
if(ret > 9)
write(ret / 10);
putchar(ret % 10 + '0');
}
int main()
{
freopen("chess.in","r",stdin);
freopen("chess.out","w",stdout);
n = read();
m = read();
min_[1] = 1;
sum[1] = 1;
for(register int i = 1;i <= n;++i)
for(register int j = 1;j <= 5000;++j)
{
sum[j] = sum[j] * 2 + k;
k = sum[j] / 10;
sum[j] %= 10;
}
for(register int i = 1;i <= 5000;++i)
{
sum[i] -= min_[i];
if(sum[i] < 0)
sum[i] += 10,--sum[i + 1];
}
t[1] = 1;
for(register int i = 1;i <= m;++i)
for(register int j = 1;j <= 5000;++j)
{
t[j] = t[j] * 2 + k;
k = t[j] / 10;
t[j] %= 10;
}
for(register int i = 1;i <= 5000;++i)
{
t[i] -= min_[i];
if(t[i] < 0)
{
t[i] += 10;
--t[i + 1];
}
}
k = 0;
for(register int i = 1;i <= 5000;++i)
{
sum[i] = sum[i] + t[i] + k;
k = sum[i] / 10;
sum[i] %= 10;
}
bool p;
for(register int i = 5000;i >= 1;--i)
{
p = sum[i] ? 1 : p;
if(p)
write(sum[i]);
}
}