Noip2015提高组解题报告

Day1

Noip2015提高组解题报告

T1神奇的幻方

一道简单异常的小模拟,我们只需要确定数字1的位置,然后根据题意枚举即可,简简单单就A了,什么也不卡。

然而这题,我刚开始学OI的时候,因为当时比较蠢,被这题花式吊打啊....根本不会啊.....

ε=(´ο`*)))唉又想起没学OI的自己了..

虽然题简单,还是惯例丢代码

 #include<bits/stdc++.h>
using namespace std;
const int maxn = ;
int a[maxn][maxn];
int n, x, y; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} int main() {
n = read();
x = , y = n / + ;
a[x][y] = ;
for(register int i = ; i <= n * n; ++i) {
if(x == && y != n) {
x = n, y += ;
a[x][y] = i;
}
else if(x != && y == n) {
x -= , y = ;
a[x][y] = i;
}
else if(x == && y == n) {
x += ;
a[x][y] = i;
}
else if(x != && y != n) {
if(!a[x - ][y + ]) {
x -= , y += ;
a[x][y] = i;
}
else if(a[x - ][y + ]) {
x += ;
a[x][y] = i;
}
}
}
for(register int i = ; i <= n; ++i) {
for(register int j = ; j <= n; ++j)
printf("%d ", a[i][j]);
printf("\n");
}
return ;
}

T2信息传递

Noip2015提高组解题报告

大意就是说一堆人会一直传话,形成一个环,问你最小的环里有多少个人

显然你可以直接tarjan跑强联通分量,当然你也可以跑并查集等做法做

并查集写法简单讲就是在路径压缩同时维护环的大小,对于给出的传递者与被传递者,判断是不是一个集合里的,不是就合并

是就更新答案

 #include<bits/stdc++.h>
#define ll long long
#define uint unsigned int
#define ull unsigned long long
using namespace std;
const int maxn = ;
const int inf = ;
int t, fa[maxn];
int dis[maxn], ans;
int n; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} int getfather(int x) {
if(x == fa[x]) return x;
int son_in_son = fa[x];
fa[x] = getfather(fa[x]);
dis[x] += dis[son_in_son];
return fa[x];
} int main() {
// freopen("message.in", "r", stdin);
// freopen("message.out", "w", stdout);
n = read();
for(register int i = ; i <= n; ++i) fa[i] = i;
ans = inf;
for(register int i = ; i <= n; ++i) {
t = read();
int u = getfather(i), v = getfather(t);
if(u != v) {
fa[u] = v;
dis[i] = dis[t] + ;
}
else ans = min(ans, dis[i] + dis[t] + );
}
printf("%d\n", ans);
return ;
}

tarjan就更简单了,跑强连通分量,统计每个环中节点的大小,然后找最小的大小不为1环的就好了

 #include<bits/stdc++.h>
#define ll long long
#define uint unsigned int
#define ull unsigned long long
using namespace std;
const int maxn = ;
const int inf = ;
struct shiki {
int net, y;
}e[maxn << ];
int lin[maxn], len = ;
int dfn[maxn], low[maxn];
int num = , cnt = , top = ;
int c_num[maxn], s[maxn];
bool in_s[maxn];
int sum[maxn];
int n, t, ans; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = ;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline void insert(int xx, int yy) {
e[++len].y = yy;
e[len].net = lin[xx];
lin[xx] = len;
} void tarjan(int x) {
dfn[x] = low[x] = ++num;
s[++top] = x, in_s[x] = ;
for(int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
tarjan(to);
low[x] = min(low[x], low[to]); }
else if(in_s[to]) low[x] = min(low[x], dfn[to]);
}
if(dfn[x] == low[x]) {
cnt++; int k;
do {
k = s[top--], in_s[k] = ;
c_num[k] = cnt;
}while(x != k);
}
} int main() {
memset(sum, , sizeof(sum));
n = read();
for(register int i = ; i <= n; ++i) {
t = read();
insert(i, t);
}
for(register int i = ; i <= n; ++i)
if(!dfn[i]) tarjan(i);
for(register int i = ; i <= n; ++i)
sum[c_num[i]]++;
ans = inf;
for(register int i = ; i <= cnt; ++i)
if(sum[i] != ) ans = min(ans, sum[i]);
printf("%d\n", (ans != inf) ? ans : );
return ;
}

T3斗地主

Noip2015提高组解题报告

Noip2015提高组解题报告

Noip2015提高组解题报告

这题当年显然恶心的不少的人

这题确实比较恶心,尤其是那诡异的一堆牌的出法,因为和真实斗地主不一样,比较不适

对于这点,我们本着:“所有题面没说是不合法的情况,都是合法的” 的原则,可以知道最烦人的大小王,他们不是对,他们是单牌,所以炸弹带大小王是合法的!

因为炸弹可以带两张单牌。

我们贪心的想一想,显然我们要想出牌次数最小,一定是要尽可能的先把所有能一次丢走顺子和带牌都出完,最后剩下的牌在甩完就好,所以我们可以爆搜顺子和带牌加上最后剩下的牌的出牌次数,答案求min

好吧和贪心并没有什么关系,我们做这题首先要有一个合理的搜索顺序:先搜顺子和带牌,最后处理剩余的牌

因为显然,除了顺子,带牌,剩下的无论是什么都可以一次出完,而相比枚举出掉什么单牌或对子或炸弹

显然顺子和带牌的情况更方便处理,这样我们就可以爆搜了,奥对,顺子和带牌先搜哪个后搜哪个都是可以A题的

 #include<bits/stdc++.h>
using namespace std;
const int maxn = ;
const int inf = ;
int sum[maxn];
int T, n, ans; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} void dfs_kill(int x) {//出牌次数
/*
可以公开的情报:
出牌方式有火箭,炸弹,单牌,对牌,三不带,三带单,三带对,
顺子,连对,三顺, 四带二(且带的两张牌不要求相同)
*/
if(x >= ans) return;
//顺子*
int op = ;//单顺
for(register int i = ; i <= ; ++i) {//2与双王不可用
if(sum[i] < ) op = ;//打断顺子
else {
op++;//长度加1
if(op >= ) {
for(register int j = i - op + ; j <= i; ++j) sum[j]--;//出牌
dfs_kill(x + );
for(register int j = i - op + ; j <= i; ++j) sum[j]++;//回溯
}
}
}
op = ;//连对
for(register int i = ; i <= ; ++i) {
if(sum[i] < ) op = ;//打断连对
else {
op++;
if(op >= ) {
for(register int j = i - op + ; j <= i; ++j) sum[j] -= ;
dfs_kill(x + );
for(register int j = i - op + ; j <= i; ++j) sum[j] += ;
}
}
}
op = ;//三顺
for(register int i = ; i <= ; ++i) {
if(sum[i] < ) op = ;
else {
op++;
if(op >= ) {
for(register int j = i - op + ; j <= i; ++j) sum[j] -= ;
dfs_kill(x + );
for(register int j = i - op + ; j <= i; ++j) sum[j] += ;
}
}
}
//带牌
for(register int i = ; i <= ; ++i) {//大小王不能带牌
if(sum[i] < ) continue;//连三带都不行的
sum[i] -= ;//大家都先搞三带
for(register int j = ; j <= ; ++j) {//三带一居然能带大小王??
if(sum[j] < || j == i) continue;
sum[j]--;
dfs_kill(x + );
sum[j]++;
}
for(register int j = ; j <= ; ++j) {//三带二,大小王不算对子
if(sum[j] < || j == i) continue;
sum[j] -= ;
dfs_kill(x + );
sum[j] += ;
}
sum[i] += ;
if(sum[i] > ) {//一些群众可以四带
sum[i] -= ;
for(register int j = ; j <= ; ++j) {//带单牌之时,大小王算单牌
if(sum[j] < || j == i) continue;
sum[j]--;
for(register int k = ; k <= ; ++k) {
if(sum[k] < || (k == j && k != ) || k == i) continue;
sum[k]--;
dfs_kill(x + );
sum[k]++;
}
sum[j]++;
}
for(register int j = ; j <= ; ++j) {//带双牌之时,大小王不算对子
if(sum[j] < || j == i) continue;
sum[j] -= ;
for(register int k = ; k <= ; ++k) {
if(sum[k] < || k == j || k == i) continue;
sum[k] -= ;
dfs_kill(x + );
sum[k] += ;
}
sum[j] += ;
}
sum[i] += ;
}
}
//已经处理完了顺子,连对,三顺,三带一,三带二,四带二单,四带二对
//对于剩下的*,显然可以一次性丢出去
for(register int i = ; i <= ; ++i) if(sum[i]) x++;
ans = min(ans, x);
} int main() {
// freopen("landlords.in", "r", stdin);
// freopen("landlords.out", "w", stdout);
T = read(), n = read();
while(T--) {
memset(sum, , sizeof(sum));
ans = inf;
for(register int i = ; i <= n; ++i) {
int which = read(), col = read();
if(which == ) sum[]++;//大小王放在同一个位置
else if(which == ) sum[]++;//塞进一个A,因为A可以丢进顺子等组合且比较大,放在后面
else sum[which]++;
}
dfs_kill();
printf("%d\n", ans);
}
return ;
}

这样我们就把Noip2015Day1给AK了,实际上就这套题的难度来看,前两题简直是送分,我写前两题甚至没超过半个小时(大概?)

最后T3确定好规则和搜索顺序,因为数据随机,所以直接爆搜并不难过。这样,你就有个极大的优势了

Day2

跳石头

Noip2015提高组解题报告

挺经典的二分答案题目(不)

二分一个距离,然后开始判定是否合法:

定义一个变量now表示现在在哪块石头上,ans表示能拿掉的石头数量

若枚举到的石头i与now的距离小于二分出的距离,将ans++;

否则令now = i;

若最后ans <= m,则距离不够(显然会有人想知道为什么ans==m不行,我们想一下,我们能够跳到一块石头为i+m,那么按照我们的判定方式,就将i+m拿掉了,然而我们能不能跳到第i+m+1块石头呢?因为必然要有一块岩石做终点,不能跳到水里,所以我们至少要能够拿掉m+1块石头才能说明我们一定能拿掉m块石头并且保证有起点和终点)

 #include<bits/stdc++.h>
using namespace std;
const int maxn = ;
int a[maxn];
int L, m, n; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline bool check(int x) {
int ans = , now = ;
for(int i = ; i <= n; ++i)
if(a[i] - now < x) ans++;
else now = a[i];
return ans <= m;
} int main() {
L = read(), n = read(), m = read();
for(int i = ; i <= n; ++i) a[i] = read();
int l = , r = L;
while(l < r) {
int mid = l + r >> ;
if(check(mid)) l = mid + ;
else r = mid - ;
}
if(!check(l)) l -= ;
printf("%d\n", l);
return ;
}

子串

Noip2015提高组解题报告

这题,还挺好写的....

高端做法不会,但是我们可以很容易的想到一个四维的状态

f[i, j, k, 0/1]表示A串取到了第i个字符,B串匹配了j个字符,使用了k个子串,第i个字符取或是不取

方程:

f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1]) % mod

如果ai != bj 则 f[i][j][k][1] = 0

否则 f[i][j][k][1] = (f[i-1][j - 1][k][1] + (f[i-1][j - 1][k - 1][0] + f[i-1][j - 1][k - 1][1]

因为空间问题(毕竟是四维嘛...)我们使用滚动数组即可

初态:f[0][0][0][0] = f[1][0][0][0] = 1

末态:f[n][m][k][0]+f[n][m][k][1]

然后就简简单单地A题了

 #include<bits/stdc++.h>
using namespace std;
const int mod = ;
const int maxn = ;
const int maxm = ;
int f[][maxm][maxm][];
char a[maxn], b[maxm];
int n, m, l, id = ; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} int main() {
// freopen("a.in", "r", stdin);
// freopen("a.out", "w", stdout);
n = read(), m = read(), l = read();
scanf("%s%s", a + , b + );
f[][][][] = f[][][][] = ;
for(register int i = ; i <= n; ++i) {
for(register int j = ; j <= m; ++j)
for(register int k = ; k <= l; ++k) {
f[i&][j][k][] = (f[(i-)&][j][k][] + f[(i-)&][j][k][]) % mod;
if(a[i] != b[j]) f[i&][j][k][] = ;
else f[i&][j][k][] = (f[(i-)&][j - ][k][] + (f[(i-)&][j - ][k - ][] + f[(i-)&][j - ][k - ][]) % mod) % mod;
}
}
printf("%d\n", (f[n & ][m][l][] + f[n & ][m][l][]) % mod);
return ;
}

运输计划

Noip2015提高组解题报告

两天来最难的题,当然对于一些大佬,这题比斗地主简单

题解:LCA+差分+二分

先咕咕咕一下回头一定补咕咕咕咕

上一篇:洛谷 P5019 铺设道路


下一篇:【比赛】NOIP2018 铺设道路