noip77
作者:互联网
T1
考场想法:暴力70pts好耶,然而事实上只有40pts,于是想拿70pts,找了半天规律,30mins后有了70pts,试图去写正解,因为感觉这种题大家应该都切了,于是继续耗了30mins,无果,70pts跑路。
好吧,确实是大家都切了,而我只有可怜的70pts,难受。
70pts:通过一些手段可以发现,左端点固定,那么会有一段区间答案是连续的,下一个不重复的右端点即为上一个区间答案+1,左端点的话有类似的, \(l|(l+1)\) 。
100pts:
题解写得挺详细
话说这种题就应该猜个结论然后走人啊...
浪费时间打表,还是菜了。
T2
考场写了个垃圾暴力,连部分分的背包都没写...
正解不会的话,还是要多考虑亿下部分分的。
30pts:普通暴力。
60pts:加个背包。
100pts:二分+单调指针乱扫。
将 \(n\) 拆成两半来写,求出前 \(\frac{n}{2}\) 和后 \(\frac{n}{2}\) 所能组成的数,然后排个序。
然后二分答案, \(check\) 时枚举在前 \(\frac{n}{2}\) 所选的数,不难发现,前半部分往后枚举,那么后半部分选的数会越来越小,所以拿个指针瞎几把扫就行。
注意对手爆蛋的情况也要算上。
Code
#include<cmath>
#include<cstdio>
#include<cctype>
#include<algorithm>
#define re register
#define int long long
const int N = 21;
#define scanf oma=scanf
using std::sort;
int oma;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
double p;
int n,w[N<<1];
#define debug(s) debug((char*)s)
auto debug = [](char* s) { printf("%s\n",s); };
}using namespace some;
namespace simple
{
int ra[1<<N];
auto solve = []() -> void
{
int sta = (1<<n)-1;
for(re int i=1; i<=sta; i++)
{
for(re int j=1; j<=n; j++)
{
if((i>>j-1)&1)
{ ra[i] += w[j]; }
}
}
sort(ra+1,ra+1+sta);
//for(re int i=0; i<=sta; i++) { printf("%d\n",ra[i]); }
int pos = ceil((sta+1)*p)-1;
printf("%lld\n",ra[pos]);
};
};
namespace bag
{
int dp[N*2000],sum,tot;
auto solve = []() -> void
{
dp[0] = 1;
for(re int i=1; i<=n; i++)
{
sum += w[i];
for(re int j=sum; j>=w[i]; j--)
{ dp[j] += dp[j-w[i]]; }
}
long long sta = 1ll<<n;
long long pos = ceil(sta*p)-1;
for(re int i=1; i<=sum; i++)
{
tot += dp[i];
if(tot>=pos) { printf("%lld\n",i); return ; }
}
};
};
namespace right
{
int res,lnt,rnt,level;
int val[1<<N],var[1<<N];
auto check = [](int mid,int pos = rnt,int sum = 0) -> bool
{
for(re int i=0; i<=lnt; i++)
{
while(pos!=-1&&val[i]+var[pos]>mid)
{ pos--; }
sum += pos+1;
}
//printf("mid=%lld sum=%lld\n",mid,sum);
return sum>=level;
};
auto solve = []() -> void
{
int mid1 = n/2,mid2 = n/2+1;
lnt = (1<<mid1)-1,rnt = (1<<(n-n/2))-1,level = ceil((1ll<<n)*p);
for(re int i=1; i<=lnt; i++)
{
for(re int j=1; j<=mid1; j++)
{ if((i>>j-1)&1) { val[i] += w[j]; } }
}
for(re int i=1; i<=rnt; i++)
{
for(re int j=mid2; j<=n; j++)
{ if((i>>j-mid2)&1) { var[i] += w[j]; } }
}
int l = 0,r = val[lnt]+val[rnt];
sort(val+1,val+1+lnt),sort(var+1,var+1+rnt);
//printf("lnt=%lld rnt=%lld level=%lld\n",lnt,rnt,level);
//for(re int i=1; i<=lnt; i++) { printf("%d ",val[i]); } printf("\n");
//for(re int i=1; i<=rnt; i++) { printf("%d ",var[i]); } printf("\n");
while(l<=r)
{
int mid = l+r>>1;
if(check(mid))
{ r = mid-1,res = mid; /*debug("fuck");*/ }
else
{ l = mid+1; }
}
printf("%lld\n",res);
};
};
namespace OMA
{
auto main = []() -> signed
{
freopen("answer.in","r",stdin); freopen("answer.out","w",stdout);
cin >> n; scanf("%lf",&p);
for(re int i=1; i<=n; i++)
{ cin >> w[i]; }
right::solve();
return 0;
};
}
signed main()
{ return OMA::main(); }
T3
考场写的还是垃圾暴力,判重用的bool开不下。
然而事实上用 \(bitset\) +大力剪枝就可以切...
一些可能没用的优化,如果想到了就加上,反正不亏。
一些STL的奇奇妙妙的东西也可以想一下。
T4
考场只写了并查集的20pts。
20pts:已述。
100pts:
如果是交集,则将新节点与集合内的点连一条边,并集则将集合内的点与新节点连一条边。
\(k=1\) 时交并等价,要建双向边。
然后直接暴力搜看是否能搜到即可ac
正解是先离线,建树求dfs序。
对于询问,如果dfs区间不存在包含关系,则答案为0.
否则的话,分情况讨论。
- \(x\) 是 \(y\) 的祖先, \(fa_{y}\) 到 \(x\) 都是“交” 答案为1,否则为0 。
- \(y\) 是 \(x\) 的祖先, \(fa_{x}\) 到 \(y\) 都是“并” 答案为1,否则为0 。
同样要特判 \(k=1\) 的情况。
以下三道为考完新加的...
T5
超级添狗题....
题解
T6
lun_runda题...
题解
T7
标签:noip77,val,int,lnt,re,70pts,lld 来源: https://www.cnblogs.com/-OMA-/p/15420029.html