NOIP2001-2017题解
NOIP2018
试题:赛道修建
预计得分:55
实际得分:55
用时:1h
思路:分析数据,可知特殊图像一共分3种:
链式图(b[i] = a[i] + 1):二分
菊花图(a[i] = 1):最长边和最短边联合,次长边和次短边联合……
普通但是只需要一条赛道(m = 1):树的直径
反思:
此题应考虑部分分,而不是最优解
最后附上代码(55分部分分)
#include<bits/stdc++.h>
using namespace std;
int n, m;
const int maxn = 1e5 * 5 + 10;
int a[maxn];
int head[maxn];
struct road{
int next_edge, to;
int w;
}edge[maxn * 2];
int tot = 0;
int tot1 = 0;
int ans = 0;
bool check(int total){//TODO check
int t = 0;
int summ = 0;
for(int i = 1;i < n;i++){
if(summ + a[i]>= total){
t++;
summ = 0;
}
else summ += a[i];
}
return t >= m;
}
void dfs(int now,int fa){//TODO dfs
for(int k = head[now];k;k = edge[k].next_edge){
int v = edge[k].to;
int w = edge[k].w;
if(v != fa){
dfs(v,now);
a[++tot1] = w;
}
}
}
int dfs2(int now,int fa){//TODO dfs2
int max1 = 0,max2 = 0;
for(int k = head[now];k;k = edge[k].next_edge){
int v = edge[k].to;
int w = edge[k].w;
if(v != fa){
max2 = max(max2,dfs2(v,now) + w);
if(max1 < max2)swap(max1,max2);
}
}
ans = max(ans,max1 + max2);
return max1;
}
void add(int u,int v,int w){//TODO add
tot++;
edge[tot].to = v;
edge[tot].next_edge = head[u];
edge[tot].w = w;
head[u] = tot;
}
struct cmp{
bool operator ()(int a,int b){
return a > b;
}
};
int main(){//TODO main
scanf("%d%d",&n,&m);
int u, v, w;
bool book1 = 1;
int sum = 0;
for(int i = 1;i < n;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
if(u != 1 && v != 1)book1 = 0;
sum += w;
}
dfs(1,0);
if(book1){
sort(a + 1,a + n,cmp());
ans = 99999999;
for(int i = 1;i <= m;i++){
ans = min(ans, a[i] + a[2 * m - i + 1]);
}
printf("%d",ans);
}
else if(m == 1){
dfs2(1,0);
printf("%d",ans);
}
else {
int l = 1,r = sum,mid;
while(l < r){
mid = (l + r + 1) / 2;
if(check(mid)) l = mid;
else r = mid - 1;
}
printf("%d",l);
}
return 0;
}
/*
5 2
1 2 5
2 3 6
3 4 3
4 5 2
5 3
1 3 9
1 2 2
1 4 8
1 5 3
*/
NOIP2017
试题:奶酪
预计得分:???
实际得分:100
用时:0.5-1h
思路:对于这道题,实际上就是相切或相交的球并联成一个集合,然后判断每一个集合是否能贯穿奶酪就完事了
反思:此题需多加思索才能想出正解
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e3 + 10;
int T;
int n;
ll r,h;
//if u state "r" as "int" instead of "long long",u will "RE"
int f[maxn];
int f1[maxn],f2[maxn];
ll x[maxn], y[maxn], z[maxn];
ll dist(ll x1,ll y1,ll z1,ll x2,ll y2,ll z2){//pay attention:this is dis square
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2);
}
int getf(int x){
if(f[x] == x)return x;
else {
f[x] = getf(f[x]);
return f[x];
}
}
int main(){
scanf("%d",&T);
while(T--){
memset(f1,0,sizeof(f1));
memset(f2,0,sizeof(f2));
scanf("%d%lld%lld",&n,&h,&r);
int top = 0, bottom = 0;
for(int i = 1;i <= n;i++){
f[i] = i;
}
ll dis = 0;
for(int i = 1;i <= n;i++){
scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
if(z[i] + r >= h){
top++;
f1[top] = i;
}
if(z[i] - r <= 0){
bottom++;
f2[bottom] = i;
}
for(int j = 1;j <= i;j++){
if((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) > 4 * r * r)
continue;//it is so large that it maybe over LONG_LONG_MAX
if(dist(x[i],y[i],z[i],x[j],y[j],z[j]) <= 4 * r * r){
int a1 = getf(i);
int a2 = getf(j);
if(a1 != a2)f[a2] = a1;
}
}
}
bool book = 0;
for(int i = 1;i <= top;i++){
for(int j = 1;j <= bottom;j++){
if(getf(f1[i]) == getf(f2[j])){
book = 1;
break;
}
}
if(book)break;
}
puts(book ? "Yes" : "No");
}
return 0;
}
试题:逛公园
预计得分
实际得分:
用时:
思路:
反思:
试题:矩形覆盖
预计得分:100
实际得分:100
用时:0.5h
思路:先建立一个矩形类型,然后dfs
对于dfs,定义两个状态:当前点和总面积
每一次枚举将当前点放入哪一个矩形
然后就完事了
反思:
代码:
#include<bits/stdc++.h>
using namespace std;
int ans = INT_MAX >> 2;//提前预定大一点防止面积过大导致出错
struct mtrx{
int left,up,right,down;// up/down:上/下(y) left/right:左/右(x) eg:左下(left,down)
bool used;
bool inmtrx(int x,int y){
return x <= right && x >= left && y <= up && y >= down;
}
int S(){
return (right - left) * (up - down);
}
bool intersect(mtrx a){
if(!a.used || !used)return 0;
return inmtrx(a.left,a.down)
|| inmtrx(a.right,a.down) || inmtrx(a.left,a.up) || inmtrx(a.right,a.up);
}
void add(int x,int y){
if(!used){
left = right = x;
up = down = y;
used = 1;
}
else{
if(y > up)up = y;
else if(y < down)down = y;
if(x > right)right = x;
else if(x < left)left = x;
}
}
void print(){
printf("\
left: down:(%d,%d)\
up :(%d,%d)\
right:down:(%d,%d)\
up :(%d,%d)\n",left,down,left,up,right,down,right,up);
}
}mtr[5];
int n,k;
int x[100],y[100];
bool check(){
for(int i = 1;i <= n;i++){//n <= 4请放心食用
for(int j = i + 1;j <= n;j++){
if(mtr[i].intersect(mtr[j])) return 0;
}
}
return 1;
}
void dfs(int step,int area){
if(area >= ans)return;
if(step == n + 1){
if(check()){
if(ans > area){
ans = area;
}
}
}
mtrx tmp;
for(int i = 1;i <= k;i++){
tmp = mtr[i];
mtr[i].add(x[step],y[step]);
dfs(step + 1,area - tmp.S() + mtr[i].S());
mtr[i] = tmp;
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;i++){
scanf("%d%d",&x[i],&y[i]);
}
dfs(1,0);
printf("%d",ans);
return 0;
}
试题:宝藏
预计得分:40
实际得分:70
用时:1h
思路:爆搜没什么好说的
反思:n!< 10 ^ 3所以有重边
代码(70分)
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m;
int wei[20][20];//wei[i][j] 是i->j的边值为wei[i][j]
int book[20];
int tmp = 0,ans = inf;
void dfs(int now,int num){
if(num == n){
if(tmp < ans) ans = tmp;
return;
}
if(tmp >= ans)return;
for(int i = 1;i <= n;i++){
if(book[i])continue;
for(int j = 1;j <= n;j++){
if(wei[j][i] == inf || !book[j] || j == i)continue;
book[i] = book[j] + 1;
tmp += wei[j][i] * book[j];
dfs(i,num + 1);
book[i] = 0;
tmp -= wei[j][i] * book[j];
}
}
}
int main(){
scanf("%d%d",&n,&m);
int u,v,w;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
wei[i][j] = inf;
}
}
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&u,&v,&w);
wei[u][v] = wei[v][u] = min(wei[u][v],w);
}
for(int i = 1;i <= n;i++){
book[i] = 1;
dfs(i,1);
book[i] = 0;
}
printf("%d",ans);
return 0;
}
题目:P7116
预计得分:40
实际得分:45
用时:1h
思路:对于每一轮移动,都有一些点会因移动后出界而无效,剩余的点的的数量即为这一轮的步数答案
反思:从最小的1维开始思考,即可得到45分答案
代码:(45分)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int k, n;
const LL mod = 1e9 + 7;
const int maxn = 5e5 + 100;
LL c[maxn],d[maxn],e[maxn];//e[i]为当前的偏移量
LL l[20], r[20];//l,r是这一轮第i维的最左端不符合(负数)的和最右端不符合的点(正数)
LL w[20];
LL ans = 1;
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= k;i++){
scanf("%lld",&w[i]);
ans = ans * w[i] % mod;
}
//此时ans = 所有点(每一个点在走出界时也算一步)
for(int i = 1;i <= n;i++){
scanf("%lld%lld",&c[i],&d[i]);
}
while(1){//枚举每一轮
for(int i = 1;i <= n;i++){
//记录偏移量
e[c[i]] += d[i];
//更新边界
l[c[i]] = min(l[c[i]],e[c[i]]);
r[c[i]] = max(r[c[i]],e[c[i]]);
LL s = 1;
for(int j = 1;j <= k;j++){
if(r[j] - l[j] >= w[j]){
printf("%lld",ans);
return 0;//所有点已经全部出界
}
s = s * (w[j] - r[j] + l[j]) % mod;//加上合格的点
}
ans = (ans + s) % mod;
//更新答案
}
bool book = 1;
for(int i = 1;i <= k;i++){
if(e[i] != 0){//如果走不出去
book = 0;
break;
}
}
if(book){
puts("-1");
return 0;
}
}
return 0;
}
详细题解见链接:
https://blog.csdn.net/hi_ker/category_9277438.html