NOIP2017模擬賽(八)總結


NOIP2017模擬賽(八)解題報告

T1:
路徑
題目描述
在二維坐標平面里有N個整數點,Bessie要訪問這N個點。剛開始Bessie在點(0,0)處。 每一步,Bessie可以走到上、下、左、右四個點。即假設Bessie當前所在點的坐標是(x,y),那么它下一步可以移動到(x,y+1), (x,y-1), (x+1,y), (x-1,y)之一。
Bessie目標是找到一個移動序列,滿足下面的條件:
1、從點(0,0)出發。
2、對於給定的那N個點,每個點至少被訪問一次。
3、可以選擇那N個給定點中任意一個作為結束點。
現在為了增加難度,農夫規定Bessie走過的所有步數之和的奇偶性必須為wantedParity,顯然wantedParity 是0或1,是題目給定的,0表示偶,1表示奇。Bessie立刻感覺到了難度的增加,如果存在滿足條件的移動序列,那么輸出CAN,否則輸出CANNOT。
輸入格式 1883.in
多組測試數據。
第一行,一個整數g,表示有g組測試數據,1 <= g <= 5。
每組測試數據格式:
第一行,N、wantedParity。 1 <= N <= 50。
接下來有N行,每行兩個整數:x, y,表示第i個點的坐標值。
范圍都在【-1000000,1000000】。輸入的N個點不會有重疊,且不會有(0,0)點。
輸出格式 1883.out
共g行,每行輸出CAN或CANNOT。
輸入樣例 1883.in
5
4 0
-1 -1
-1 1
1 1
1 -1
3 1
-5 2
-3 0
2 3
2 1
1001 0
-4000 0
3 0
11 -20
21 42
0 7
2 1
0 10
6 -20
輸出樣例 1883.out
CAN
CAN
CAN
CANNOT
CANNOT
樣例解釋
第一組測試數據:其中一個合法序列如下:
•2 steps: (0,0) -> (-1,-1).
•2 steps: (-1,-1) -> (-1,1).
•2 steps: (-1,1) -> (1,1).
•2 steps: (1,1) -> (1,-1).
第二組測試數據:其中一個合法序列:
•7 steps: (0,0) -> (-5,2).
•4 steps: (-5,2) -> (-3,0).
•8 steps: (-3,0) -> (2,3).
第三組測試數據:其中一個合法序列:
(0,0) -> (-4000,0) -> (1001, 0)。
題目分析:拿到這題的時候,本着第一題都是水題的思想,我提出了一個大膽的結論:從(0,0)開始,每個點走一遍的路徑的奇偶性,只跟這條路徑的終點有關。然而我覺得還是穩一點比較好,於是我開始嘗試着證明。首先我們發現,從點(a1,b1)走到點(a2,b2),再走回來,路徑長度一定是偶數,因為如果(a1-a2+b1-b2)是奇/偶數,那么去回都一定要走奇/偶數步。這樣從一個點出發,經過很多個點最后再回來,走的步數一定是偶數。既然這樣,中間每個點經過多少次,是否重復走就不重要了。而我們現在以任意一個點(a,b)為終點,就是相當於最后不用走(a,b)->(0,0)這條路。如果這條路長度是奇/偶數,那么就可以使路徑總長度為奇/偶數。於是我們掃一遍每個點的坐標判斷一下即可。
CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

int g,N,wantedParity;
bool odd,even;

int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);

scanf("%d",&g);
while (g--)
{
scanf("%d%d",&N,&wantedParity);
odd=even=false;
while (N--)
{
int x,y;
scanf("%d%d",&x,&y);
x=x+y+20000000;
x=(x&1);
if (x) odd=true;
else even=true;
}
if ( ( !wantedParity && !even ) || ( wantedParity && !odd ) ) printf("CANNOT\n");
else printf("CAN\n");
}

return 0;
}

T2:
冠軍
題目描述
有N個拳手參加擂台賽,這個人的編號是0至N-1。有N個位置,編號從0至N-1。每個位置分配一個拳手,顯然共有N!種不同的分配方案。
對於一種具體的分配方案,站在位置0的拳手與站在位置1的拳手比賽,勝者進入下一輪,敗者被淘汰。站在位置2的拳手與站在位置3的拳手比賽,勝者進入下一輪,敗者被淘汰,同樣道理,站在位置4的拳手與站在位置5的拳手比賽,勝者進入下一輪,敗者被淘汰。。。最終會產生一個冠軍拳手。
如下圖所示:

已知N一定是2的若干次冪,而且不超過16,也就是說N是{2,4,8,16}之中的某一個數。
現在的問題是:有多少種不同的分配方案,使得第i個選手能最終成為冠軍?不妨假設該數值是ans[i]。
你的任務就是輸出:ans[0]、ans[1]、….ans[N-1]。
輸入格式 1792.in
第一行,一個整數N。 N=2或者4或者8或者16。
接下來是N行N列的字符矩陣,第i行第j列的字符如果是’Y’,表示第i個拳手如果跟第j個拳手直接比賽的話,第i個拳手會贏第j個拳手,如果字符是‘N’,表示第i個拳手會輸給第j個拳手。
注意:
1、第i行第i列的字符一定是’N’
2、拳手的勝負不一定滿足傳遞性。例如:第i個拳手能贏第j個拳手,第j個拳手能贏第k個拳手,但第i個拳手可能會輸給第k個拳手。
3、如果第i行第j列的字符是’Y’,那么第j行第i列的字符一定是’N’,即拳手i和拳手j比賽,有且只有一個勝者。
輸出格式 1792.out
共N行,第i行是ans[i]。
輸入樣例 1792.in
輸入樣例一:
2
NN
YN
輸入樣例二:
4
NYNY
NNYN
YNNY
NYNN
輸入樣例三:
8
NYNYNYNY
NNYNYNYY
YNNNNNNN
NYYNNYNY
YNYYNYYY
NYYNNNNN
YNYYNYNN
NNYNNYYN
輸出樣例 1792.out
輸出樣例一:
0
2
輸出樣例二:
8
0
16
0
輸出樣例三:
4096
8960
0
2048
23808
0
1408
0
【樣例解釋】
第一樣例:不管拳手1站在位置0還是站在位置1,都能戰勝拳手0。
題目分析:這題應該是這次比賽唯一一道有點思維難度的題目了吧,還是挺有趣的。
如果用16!的暴搜肯定超時,於是我開始考慮記憶化搜索。假設我們要求16個人中每個人獲勝的方案數,我們可以在其中選8個人跑到上半部分比賽,另外8個人跑到下半部分比賽,這就變成了兩個子問題。最后我們枚舉上下半部分是誰獲勝,合並起來即可。
現在問題來了,這樣的時間復雜度是多少呢?我們知道記憶化搜索的本質是在填充f數組。在這題中,只有當s的二進制位中有 2k 個1時,f[s]才會被計算到。假設N=16,當s中有16個1時,我們要枚舉選哪8個1放到上半部分;當s中有8個1時,我們要枚舉選哪4個1放到上半部分……。這樣的總時間就是:

(C1616C816+C816C48+C48C24+C24C12)162

然而這樣算出來的時間很卡,我造了個極限數據,跑了3s。
所以我們要剪枝。但左邊括號里的這塊肯定省不了,於是我們看看右邊。 162 的時間在於我要枚舉左半部分s1誰獲勝(假設是i),右半部分s2誰獲勝(假設是j),來更新f[s]。但事實上只有i在s1中,j在s2中時,枚舉才是有用的。於是我們預處理出對於每一個s,有哪些人在它的集合中,分別是誰,合並的時候只枚舉這些人,就可以大大提高效率。事實上,加了這個強有力的剪枝之后,我的極限數據不到0.3s就跑出來了。
CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=18;
const int maxs=(1<<16)+100;
typedef long long LL;

struct data
{
LL num[maxn];
bool vis;
} f[maxs];

int P[maxs];
int Q[maxs][maxn];
bool win[maxn][maxn];
int N;

void Dfs1(int );

void Dfs2(int ori,int S,int s,int Left)
{
if (!Left)
{
Dfs1(s);
int t=ori^s;
Dfs1(t);
int mi=Q[s][0],mj=Q[t][0];

for (int i=1; i<=mi; i++)
for (int j=1; j<=mj; j++)
{
int a=Q[s][i],b=Q[t][j];
int y=a;
if (!win[a][b]) y=b;
f[ori].num[y]+=f[s].num[a]*f[t].num[b];
}
return;
}
int cnt=P[S];
while (cnt>=Left)
{
int v=S&(-S);
S^=v;
Dfs2(ori,S,s+v,Left-1);
cnt--;
}
}

void Dfs1(int s)
{
if (f[s].vis) return;
int cnt=P[s];
if (cnt==1)
{
for (int i=0; i<N; i++) if (s&(1<<i)) f[s].num[i]++;
f[s].vis=true;
return;
}
cnt>>=1;
Dfs2(s,s,0,cnt);
f[s].vis=true;
}

int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);

scanf("%d",&N);
for (int i=0; i<N; i++)
for (int j=0; j<N; j++)
{
char c=getchar();
while ( c!='Y' && c!='N' ) c=getchar();
if (c=='Y') win[i][j]=true;
else win[i][j]=false;
}

for (int i=1; i<maxs; i++) P[i]=P[ i-(i&(-i)) ]+1;
for (int i=1; i<maxs; i++)
for (int j=0; j<16; j++)
if ( i&(1<<j) ) Q[i][ ++Q[i][0] ]=j;

int ms=(1<<N)-1;
Dfs1(ms);
for (int i=0; i<N; i++) cout<<f[ms].num[i]<<endl;

return 0;
}

T3:
指紋
題目描述
隨着科技的發展,當今很多企業向社會推出了越來越多的結合指紋識別技術的高科技產品。其中以需要進行身份驗證或身份識別類型的產品居多,如門禁系統、手提電腦、指紋硬盤。
對任何生物識別系統來說,輸入數據的質量對於系准確率有着重大的影響。為了獲得較高質量的指紋圖像,近年來指紋采集設備不斷地被更新,各種先進的指紋采集技術也逐漸被引入到實際產品中。盡管如此,由於手指皮膚情況、空氣濕度、灰塵等一些因素的影響,依舊存在着大量的質量不高的指紋圖像。
通常我們可以通過編號為A,B,C,D的四個屬性來評估一個指紋圖像的質量的高低:
A為雜點的數量;B為折痕的數量;C為脊線斷續程度;D為脊線粘連程度。這四個屬性值越小表示該圖像在相應方面表現越優。
由於指紋圖質量評估研究的需要,我們通過對一個人的指紋進行多次采樣后得到多個不同質量的指紋圖像,並將其各質量屬性記錄在一個數據庫中(不同圖像的各屬性值均不相同)。對於兩個指紋圖像,單個屬性的好壞並不能說明圖像質量的高低。比如圖像1雜點數比圖像2的少,但有可能圖像1的粘連程度比圖像2高得多,因此不能武斷地認為圖像1就比圖像2好。
但是如果一個圖像有不少於三個屬性都優於另一個圖像,那么我們有理由相信前者的質量要優於后者。對於數據庫中的一個指紋圖像I,如果存在另一個圖像J,J有不少於三個質量屬性優於圖像I,那么我們認為圖像I是一種“累贅”。
為了減少指紋圖像數據庫的大小,我們希望去除其中的累贅圖像,現在實驗室需要你的幫忙,找出該部分圖像。為方便就算,我們已經分別按四個屬性,計算出了所有圖像按該屬性的優劣程度排序的名次。
輸入格式 1884.in
第一行,包含一個正整數N,表示有N張指紋圖像,編號為1,2,3…N。1 <= N <= 100000。
接下來的N行,每行有4個正整數Ai,Bi,Ci,Di。第i行表示編號為i的圖像在所有圖像中,其A屬性排名為Ai, B屬性排名為Bi, C屬性排名為Ci, D屬性排名為Di。所有Ai取遍1到N這N個自然數,1表示最優,N表示最差,類似地,Bi,Ci,Di也一樣。
輸出格式 1884.out
第一行,一個整數M,表示累贅的指紋圖像個數。接下來有M行,每行包含一個整數,表示累贅的圖像的編號。標號從小到大輸出。
輸入樣例 1884.in
6
1 1 2 6
2 3 3 4
3 4 1 3
4 2 6 5
5 6 5 1
6 5 4 2
輸出樣例 1884.out
4
2
4
5
6
題目分析:本次比賽我的做題順序是1->2->3,做這題的時候我還有80min。我一開始往CDQ分治去想。但后來我發現它不是要你求對於每個i有多少個j使它成為累贅,而是問有沒有j。這就是一道水題了嘛……我們在a,b,c,d中枚舉三個參數(假設是a,b,c)。接下來我們將數據按a值排序,然后從左到右加進treap里,treap的key值為b,維護一個附加域c,並保存子樹中c的最小值minc。這樣我們做到第i個的時候,treap中的元素一定是比它的a值要小的,然后我們用log(n)的二叉查找,找所有b比它小的元素中c的最小值,看看是否小於i的c值即可。時間復雜度O(nlog(n))(話說這就是數據結構裸題啊)。
CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
#include<ctime>
using namespace std;

const int maxn=100100;

int Min(int x,int y)
{
if (x<y) return x;
return y;
}

struct Tnode
{
int valC,valB,minB,fix;
Tnode *lson,*rson;
void Up()
{
minB=valB;
if (lson) minB=Min(minB,lson->minB);
if (rson) minB=Min(minB,rson->minB);
}
} tree[maxn];
Tnode *Root;
int cur;

struct data
{
int A,B,C,id;
} pic[maxn];
bool vis[maxn];

int a[maxn];
int b[maxn];
int c[maxn];
int d[maxn];

int n;

bool Comp(data x,data y)
{
return x.A<y.A;
}

int Find(Tnode *P,int v)
{
if (!P) return n+1;
if ( P->valC < v )
return Min( Min( Find(P->rson,v) , P->valB ) , (P->lson? P->lson->minB:n+1) );
else return Find(P->lson,v);
}

Tnode *New_node(int vc,int vb)
{
cur++;
tree[cur].valC=vc;
tree[cur].valB=tree[cur].minB=vb;
tree[cur].fix=rand();
tree[cur].lson=tree[cur].rson=NULL;
return tree+cur;
}

void Right_turn(Tnode *&P)
{
Tnode *W=P->lson;
P->lson=W->rson;
W->rson=P;
P=W;
P->rson->Up();
P->Up();
}

void Left_turn(Tnode *&P)
{
Tnode *W=P->rson;
P->rson=W->lson;
W->lson=P;
P=W;
P->lson->Up();
P->Up();
}

void Insert(Tnode *&P,int vc,int vb)
{
if (!P) P=New_node(vc,vb);
else
if ( vc < P->valC )
{
Insert(P->lson,vc,vb);
if ( P->lson->fix < P->fix ) Right_turn(P);
else P->Up();
}
else
{
Insert(P->rson,vc,vb);
if ( P->rson->fix < P->fix ) Left_turn(P);
else P->Up();
}
}

void Solve()
{
sort(pic+1,pic+n+1,Comp);
Root=NULL;
cur=-1;
for (int i=1; i<=n; i++)
{
int x=Find(Root,pic[i].C);
if (x<pic[i].B) vis[ pic[i].id ]=true;
Insert(Root,pic[i].C,pic[i].B);
}
}

int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);

srand( time(0) );
rand(); rand();

scanf("%d",&n);
for (int i=1; i<=n; i++)
scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);

for (int i=1; i<=n; i++)
pic[i].A=a[i],pic[i].B=b[i],pic[i].C=c[i],pic[i].id=i;
Solve();
for (int i=1; i<=n; i++)
pic[i].A=a[i],pic[i].B=b[i],pic[i].C=d[i],pic[i].id=i;
Solve();
for (int i=1; i<=n; i++)
pic[i].A=a[i],pic[i].B=c[i],pic[i].C=d[i],pic[i].id=i;
Solve();
for (int i=1; i<=n; i++)
pic[i].A=b[i],pic[i].B=c[i],pic[i].C=d[i],pic[i].id=i;
Solve();

int num=0;
for (int i=1; i<=n; i++) if (vis[i]) num++;
printf("%d\n",num);
for (int i=1; i<=n; i++) if (vis[i]) printf("%d\n",i);

return 0;
}

總結:AK了,沒什么值得高興的。第一題裸題,第三題模板題,第二水題。我反而覺得我寫數據生成器的能力要加強。第三題對拍的時候,我的方法是先令 a[i]=b[i]=i,c[i]=d[i]=Ni+1 ,然后隨機交換a,b,c,d中的兩個元素N/2次,作為數據。但這樣生成的數據非常弱,比如N=5000時,答案基本等於4998這個值。這或許就是隨機化的弊端吧,又或者是我這種生成數據的方法不優……還是要繼續探索一下別的方法。(好像從剩下的數中隨機選一個數作為a[i]的這種方法也不優啊)


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com