NOIP2014复盘
D1T1 P1328 生活大爆炸版剪刀石头布
他们按照周期出圈,按照表里写的去判断即可。签到题。
D1T2 P1351 联合权值
这道题的突破口在于距离为2是什么意思。我们先解决第二问。
在一颗有根树中,显然要不是爷孙关系,要不是兄弟关系。
爷孙关系好处理,遍历一下乘上去就完事。重点是兄弟关系如何处理。
有一个方便处理的结论:和的平方减去每个数分别的平方和,会等于两倍的所有数两两组合的乘积。
所以我们每到一层,就记录该层的和与平方和,通过计算就能获得兄弟的联合权值之和。
接下来解决第一问。
爷孙关系的最大值好找,每次都能独立地分析,直接取最大即可。但兄弟的最大值不好找。
可以发现,你只需要记录每一层中的最大值和次大值即可。直接分情况暴力维护就完事了。
绿题。
/*************************************************************************
@Author: Garen
@Created Time : Fri 22 Mar 2019 11:38:41 PM CST
@File Name: P1351.cpp
@Description:
************************************************************************/
#include
using std::cin;
using std::cout;
using std::endl;
#define ll long long
const int maxn = 200005;
const ll MOD = 10007;
std::vector G[maxn];
int n;
ll ans, maxv;
int fa[maxn], grandfa[maxn];
ll w[maxn];
void dfs(int u, int f) {
fa[u] = f; grandfa[u] = fa[f];
if(grandfa[u]) {
ans += w[grandfa[u]] * w[u] * 2; ans %= MOD;
maxv = std::max(maxv, w[grandfa[u]] * w[u]);
}
ll squaresum = 0, sum = 0;
ll zuida = -1, cida = -2;
for(auto v: G[u]) {
if(v == f) continue;
sum += w[v]; sum %= MOD;
squaresum += w[v] * w[v]; squaresum %= MOD;
if(w[v] > zuida) {
cida = zuida; zuida = w[v];
} else if(w[v] > cida) {
cida = w[v];
}
dfs(v, u);
}
ans += sum * sum - squaresum; ans %= MOD;
if(zuida > 0 && cida > 0) maxv = std::max(maxv, zuida * cida);
}
int main() {
cin >> n;
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
G[u].push_back(v); G[v].push_back(u);
}
for(int i = 1; i <= n; i++) cin >> w[i];
dfs(1, 0);
cout << maxv << ' ' << ans << endl;
return 0;
}
D1T3 P1941 飞扬的小鸟
暴力去bfs还能拿挺多的分的。但要拿正解要dp。
下降可以无限下降,上升只能上升一次,这不就是01背包和完全背包吗?
我们记录\(dp[i][j]\)为到了第\(i\)条管子,现在高度为\(j\)的跳跃最少次数。
转移的时候挺有技巧的,这里提醒一下:
- 完全背包不需要枚举\(k\),不然会超时。正确做法是转移完全背包的时候从下往上去转移,像普通完全背包一样利用重复状态。
- 我们在转移的时候可以先视当前位置的管道不见,只要后面不转移到管道,我们这一层再设回0x3f3f3f3f就行了。
- 先转移完全背包,再转移01背包。
- 因为跳不出天花板,所以最高的一格需要去特判,直接从下面能转移的都转移来即可。
最后的答案就是最后一条管的所有dp的最小值,如果无解只要从后面往前找到第一个有解的就行了。
#include
using std::cin;
using std::cout;
using std::endl;
const int maxn = 10005, maxm = 1005;
const int INF = 0x3f3f3f3f;
int down[maxn], up[maxn];
bool b[maxn];
int fly[maxn], drop[maxn];
int dp[maxn][maxm];
int sum[maxn];
int n, m, p;
int main() {
std::ios::sync_with_stdio(false);
cin >> n >> m >> p;
for(int i = 0; i < n; i++) cin >> fly[i] >> drop[i];
for(int i = 1; i <= p; i++) {
int x; cin >> x; cin >> down[x] >> up[x];
b[x] = true;
}
for(int i = 0; i <= n; i++) {
if(!b[i]) {
down[i] = 0; up[i] = m + 1;
}
}
sum[0] = b[0];
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + b[i];
memset(dp, 0x3f, sizeof dp);
for(int i = down[0] + 1; i < up[0]; i++) dp[0][i] = 0;
for(int i = 1; i <= n; i++) {
for(int j = fly[i - 1]; j <= m; j++) {
if(j == m) {
for(int k = m - fly[i - 1]; k <= m; k++) {
dp[i][j] = std::min(dp[i][j], dp[i - 1][k] + 1);
dp[i][j] = std::min(dp[i][j], dp[i][k] + 1);
}
}
dp[i][j] = std::min(dp[i][j], dp[i - 1][j - fly[i - 1]] + 1);
dp[i][j] = std::min(dp[i][j], dp[i][j - fly[i - 1]] + 1);
}
for(int j = down[i] + 1; j < up[i]; j++) {
if(j + drop[i - 1] >= up[i - 1] || j + drop[i - 1] <= down[i - 1]) continue;
dp[i][j] = std::min(dp[i][j], dp[i - 1][j + drop[i - 1]]);
}
for(int j = 1; j <= down[i]; j++) dp[i][j] = INF;
for(int j = up[i]; j <= m; j++) dp[i][j] = INF;
}
int ans = INF;
for(int i = 0; i <= m; i++) ans = std::min(ans, dp[n][i]);
if(ans == INF) {
cout << 0 << endl;
for(int i = n; i >= 1; i--) {
for(int j = down[i] + 1; j < up[i]; j++) {
if(dp[i][j] < INF) {
cout << sum[i] << endl;
return 0;
}
}
}
} else cout << 1 << endl << ans << endl;
return 0;
}
D2T1 P2038 无限网络发射器选址
直接去暴力枚举正方形的位置即可,但是也要注意细节,注意不要数组越界了。
/*************************************************************************
@Author: Garen
@Created Time : Sat 09 Mar 2019 12:03:14 AM CST
@File Name: P2038.cpp
@Description:
************************************************************************/
#include
#include
#define ll long long
const ll maxn = 130;
ll a[maxn][maxn];
ll n, d;
ll cal(ll x, ll y, ll xx, ll yy) {
return a[xx][yy] - a[x - 1][yy] - a[xx][y - 1] + a[x - 1][y - 1];
}
int main() {
scanf("%lld %lld", &d, &n);
for(ll i = 1; i <= n; i++) {
ll x, y, k; scanf("%lld %lld %lld", &x, &y, &k);
a[x + 1][y + 1] = k;
}
for(ll i = 1; i <= 129; i++) {
for(ll j = 1; j <= 129; j++) {
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + a[i][j];
}
}
ll ans = -1, cnt = 0;
for(ll i = 1; i <= 129; i++) {
for(ll j = 1; j <= 129; j++) {
ll res = cal(std::max(1ll, i - d), std::max(1ll, j - d), std::min(129ll, i + d), std::min(129ll, j + d));
if(res > ans) {
ans = res; cnt = 1;
} else if(res == ans) cnt++;
}
}
printf("%lld %lld\n", cnt, ans);
return 0;
}
D2T2 P2296 寻找道路
第一个条件说所有点的出边指向的点都直接或间接的与终点连通。
其实只要建立反向图,从终点开始遍历,所有搜到的点就都是满足此条件的。
接下来第二个条件就是sb了,直接从起点开始,在合法点上跑最短路即可。
D2T3 P2312 解方程
要直接写高精然后暴力算显然不现实,我们试着每步取膜的计算。
只要膜足够大并且是质数,可靠性还是超级高的。
显然一定要遍历从\(1\)到\(m\)的每个\(x\)带进去计算。接下来就是如何判断是解。
高中数学必修3有学的话就知道有秦九韶公式,用递推的方式快速计算多项式的值。秦九韶nb
但是似乎秦九韶公式怎么写的还对答案有影响。。。这是玄学
有解就输出,其实知道了就没什么难度。
#include
#define ll long long
const ll maxn = 105, mod = 1000000007, maxm = 1e6 + 5;
ll n, m;
ll a[maxn];
ll b[maxm], cnt;
ll read()
{
ll ans = 0, s = 1;
char ch = getchar();
while(ch > '9' || ch < '0')
{
if(ch == '-') s = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
ans = (ans * 10 + ch - '0') % mod;
ch = getchar();
}
return s * ans;
}
ll f(int x)
{
int ans = 0;
for(int i = n; i >= 1; i--) ans = ((a[i] + ans) * x) % mod;
ans = (ans + a[0]) % mod;
return ans;
}
int main()
{
n = read(), m = read();
for(int i = 0; i <= n; i++) a[i] = read();
for(int i = 1; i <= m; i++)
{
if(f(i) == 0) b[cnt++] = i;
}
printf("%lld\n", cnt);
for(int i = 0; i < cnt; i++) printf("%lld\n", b[i]);
return 0;
}