NOIP2018 部分题解
心血来潮看了看18年的联赛,感觉自己进步很大= =
Day1
T1
对于一个“波”来说,显然需要的次数为最大的数(波峰),对于多个“波”,就每次记录一下从波底到波峰的高度差即可,这可以用差分简单实现
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn=100005;
int a[maxn], n;
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int ans = a[1];
for(int i=2;i<=n;i++){
if(a[i] > a[i-1]){
ans += a[i] - a[i-1];
}
}
printf("%d\n",ans);
return 0;
}
T2
显然两种系统\(a,b\)等价当且仅当\(a\)中的数字都能被\(b\)中的数字通过线性组合得到,对1~25000的数跑一个类似于完全背包的dp即可
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f;
int n;
int dp[25005],a[105];
void solve(){
memset(dp,0,sizeof dp);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
int ans = 0;
for(int i=1;i<=n;i++){
if(dp[a[i]])continue;
++ ans;
dp[a[i]] = 1;
for(int j=1;a[i]+j<=25000;j++)dp[a[i] + j] |= dp[j];
}
printf("%d\n",ans);
}
signed main(){
int te;scanf("%d",&te);
while(te --)solve();
return 0;
}
T3
给一个带边权的树,要划分出\(m\)个边不相交的路径,使得路径权值的最小值最大
首先二分答案mid,考虑树形dp,需要维护一个当前结点为根的子树的答案,以及在这种情况下能够向上传的路径权值的最大值
设\(dp[i]\)表示以 i 为根的答案,\(mx[i]\)表示 i 结点的子树能向上传的路径权值的最大值(dfs时顺便加上(i,fa[i])的权值)
\(dp[i] = \sum dp[u] + C\),其中 C 需要我们将每个儿子向上传的权值两两配对,使权值达到mid,以及在答案最优时向上传的权值要最大
首先想到的是暴力枚举向上传的边,这里有一个精妙的实现:for(int i=-1;i<(int)mp.size();i++)
,这样就涵盖了恰好两两匹配的情况。注意(int)必加!!因为\(vector<>.size()\)是unsigned,不能判断负数,时间复杂度是非常不满的O(n^2logn),结合树的直径可以拿到90pts的好成绩
发现枚举向上传的边这一步是有单调性的,很明显如果上传了边权为w的边,则比w小的边也一定可以上传,于是可以二分上传的边,时间复杂度大概是两个log,可以过
注意vector
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <set>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f,maxn=50005;
int n,m;
vector<pii>g[maxn];
int dp[maxn], mx[maxn];
void dfs(int x,int lim,int fat = -1){
if(~fat && g[x].size() == 1)return ;
vector<int> mp;
for(pii now : g[x]){
int u = now.first, w = now.second;
if(u == fat)continue;
dfs(u, lim, x);
mx[u] += w;
if(mx[u] >= lim)++ dp[x]; // 自成一派
else mp.push_back(mx[u]);
dp[x] += dp[u];
}
sort(mp.begin(),mp.end());
int mpc = mp.size();
int curid = -2, curmx = -inf;
int le = 0, ri = mpc-1, rans;
// 单独考虑全配对情况
int l=0;
int r=mpc-1;
int curres = 0;
while(l < r){
if(mp[l] + mp[r] >= lim)++l, -- r,++ curres;
else ++ l;
}
if(curres > curmx)curmx = curres, curid = -1;
else if(curres == curmx)curid = -1;
// 二分上传哪条边
while(le <= ri){
int mid = le + ri >> 1;
int l = mid == 0 ? 1 : 0;
int r = mid == mpc-1 ? mpc-2 : mpc-1;
int curres = 0;
while(l < r){
if(l == mid){
++l;continue;
}
if(r == mid){
-- r;continue;
}
if(mp[l] + mp[r] >= lim)++l, -- r,++ curres;
else ++ l;
}
if(curres > curmx)curmx = curres, curid = mid, le=mid+1;
else if(curres == curmx)curid = mid, le=mid+1;
else ri = mid-1;
}
if(curid == -2){ // 没有配对,直接取最大
mx[x] = mp[mp.size() - 1];
return ;
}
if(curid == -1){ // 全配对
mx[x] = 0;
dp[x] += curmx;
return ;
}
mx[x] = mp[curid]; // 部分配对
dp[x] += curmx;
}
int check(int lim){
for(int i=1;i<=n;i++)dp[i] = mx[i] = 0;
dfs(1, lim);
if(dp[1] >= m)return 1;
return 0;
}
signed main(){
// freopen("P5021_5.in","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
g[x].push_back(mpr(y,z)), g[y].push_back(mpr(x, z));
}
int l = 1, r = 1e9, ans;
while(l <= r){
int mid=l+r>>1;
if(check(mid))l = mid+1, ans = mid;
else r = mid-1;
}
printf("%d\n",ans);
return 0;
}
Day2
T1
树的60pts很显然,考虑基环树的部分
暴力枚举环上的断边(即从哪条边返回环的起点),变成树即可
注意在环上贪心是不对的,因为环上可能有其它的“叉出去”的边
代码:
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <map>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn=5005;
int n,m;
vector<int>g[maxn];
map<pii,int>ban;
namespace brute{
int ans[maxn], acnt=0;
void dfs(int x,int fat = -1){
ans[++ acnt] = x;
pair<int,int>pc[maxn];
for(int u : g[x]){
if(u == fat)continue;
dfs(u, x);
}
}
};
namespace solve{
int vis[maxn],pre[maxn],nxt[maxn];
int dfn[maxn], dfs_clock=0, cyc[maxn], res[maxn];
int ans[maxn],acnt=0;
pii bkpoint;
void dfs(int x,int fat = -1){
dfn[x] = ++dfs_clock;
// printf("dfs %d %d\n",x,fat);
vis[x] = 1;
for(int u : g[x]){
if(u == fat)continue;
if(vis[u]){bkpoint = mpr(x,u);continue;}
dfs(u, x);
pre[u] = x;
}
}
int gg = -1;
void dfs2(int x,int fat = -1){
for(int u : g[x]){
if(u == fat)continue;
if(ban.count(mpr(x,u)))continue;
ans[++acnt] = u;
dfs2(u, x);
}
}
void main(){
memset(res, 0x3f,sizeof res);
dfs(1);
if(dfn[bkpoint.first] > dfn[bkpoint.second])swap(bkpoint.first,bkpoint.second);
int u = bkpoint.first, v = bkpoint.second;
while(u != v){
cyc[v] = 1;
nxt[pre[v]] = v;
v = pre[v];
}
cyc[u] = 1;
v = bkpoint.second;
while(u != v){
acnt=0;
memset(vis,0,sizeof vis);
ban.clear();
ban[mpr(v, pre[v])] = ban[mpr(pre[v], v)] = 1;
ans[++acnt] = 1;
dfs2(1);
int win = 1;
for(int i=1;i<=acnt;i++){
if(res[i] < ans[i]){win = 0;break;}
if(res[i] > ans[i])break;
}
if(win)memcpy(res, ans, sizeof ans);
v = pre[v];
}
for(int i=1;i<=acnt;i++)printf("%d ",res[i]);
}
};
signed main(){
// freopen("travel.in","r",stdin);
// freopen("travel.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
g[x].push_back(y), g[y].push_back(x);
}
for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
if(m == n-1){
brute::dfs(1);
for(int i=1;i<=brute::acnt;i++)printf("%d ",brute::ans[i]);puts("");
return 0;
}
solve::main();
return 0;
}
T2
一眼打表找规律
暴力打表5 5就出不来了,考虑分析一下性质
- 对于所有副对角线从上到下填的数一定单调不减
- 如果(x-1,y) = (x,y-1),那么矩阵(x,y) -- (n,m)一定都有副对角线填的数都相同(如果不相同,一定可以有两种对称情况,字典序不变,而数字大小正好反过来了)
每次维护一个b[x][y]表示以(x,y)为左上角的矩阵是否满足性质2,可以对列维护一个二进制数,每次判断可以直接 & 一下
跑出来之后发现当 \(m>n+1\)之后\(Ans(n,m)=3 * Ans(n,m-1)\)
打表代码:
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f,maxn=20005;
int n,m;
int a[105][105],b[105][105];
int g[105][105];
int check(int x,int y){
a[x][y] = a[x+1][y] | (g[x][y] << (n-x));
if(y == m)b[x][y] = 1;
else b[x][y] = b[x][y+1] && (a[x][y+1] >> 1) == a[x+1][y];
if(x < n && y > 1 && !b[x+1][y] && g[x][y] == g[x+1][y-1])return 0;
return 1;
}
int ans=0;
void dfs(int x,int y){
if(y == 0){
if(x == 1)++ ans;
else dfs(x-1, m);
return ;
}
if(x == n || y == 1 || g[x+1][y-1] == 1){
g[x][y] = 1;
if(check(x,y))dfs(x,y-1);
}
g[x][y] = 0;
if(check(x,y))dfs(x,y-1);
}
signed main(){
freopen("5023.out","w",stdout);
for(n=2;n<=9;n++)
for(m=2;m<=9;m++){
ans=0;
memset(a,0,sizeof a),memset(b,0,sizeof b),memset(g,0,sizeof g);
// scanf("%d%d",&n,&m);
dfs(n,m);
printf("(%d,%d): %d\n",n,m,ans);
}
return 0;
}
代码:
#include <cstdio>
#include <iostream>
#define pii pair<int,int>
#define mpr make_pair
using namespace std;
const int mod = 1e9+7;
int pw(int x,int y){
if(!y)return 1;
if(y == 1)return x;
int mid = pw(x, y>>1);
if(y&1)return 1ll*mid*mid%mod*x%mod;
return 1ll*mid*mid%mod;
}
int main(){
int n,m;scanf("%d%d",&n,&m);
if(n>m){int t=n;n=m;m=t;}
if(n <= 3 && m <= 3){
if(n == 2 && m == 2)puts("12");
else if(n == 3 && m == 3)puts("112");
else if((n==2&&m==3) || (n==3&&m==2))puts("36");
else printf("%d\n",pw(2,m));
return 0;
}
if(n==2)printf("%d\n",4ll*pw(3, m-1)%mod);
else if(n==3)printf("%d\n",112ll*pw(3,m-3)%mod);
else{
pii tb[]={mpr(912,2688),mpr(7136,21312),mpr(56768,170112),mpr(453504,1360128),mpr(3626752,10879488)};
if(n == m)printf("%d\n",tb[n-4].first);
else if(m == n+1)printf("%d\n",tb[n-4].second);
else printf("%d\n",1ll * tb[n-4].second * pw(3,m-n-1) % mod);
}
return 0;
}