CSP-S模拟14 莓良心 尽梨了 团不过 七负我
T1[贪心/暴力枚举优化/单峰函数三分]给出若干个一维坐标系上的点,每个点可以选择的位置区间是[li,ri],求最小的ans=\(\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}|ai-aj|\)。(n<=5e5)
一:贪心:
找到区间最小的右边界minr和最大的左边界maxl,如果minr>=maxl,说明任意区间可以找到公共点,选择一个公共位置然后结束;
否则minr取r,maxl取l,删除这两个点继续递归寻找。一定能构造最优解。
因为对于(1)maxl<=minr,一定相交,devote=0
(2)maxl>minr
发现如果按照maxl,minr把点对分成3个区间,那么除了maxl,其他的点一定在右边的2个区间,除了minr,其他的点一定在左边2个区间,所以2个点取边界一定比往边上靠更优。
所以对于后续所有点,最优决策一定是在中间区间(也只有中间有点了...),贡献就是(l-r)(n-2i)别忘了加上自己+1.
from Catherine
点击查看代码
int main()
{
int n = read();
for(int i=1; i<=n; i++)
{
ql.push(read()); qr.push(read());
}
ll ans = 0; int l = 1e8, r = 0;
for(int i=1; i<n; i++)
{
l = min(l, ql.top()); ql.pop();
r = max(r, qr.top()); qr.pop();
if(l <= r) break;
ans += 1ll * (l-r) * (n-i*2+1);
}
printf("%lld\n", ans);
return 0;
}
二:单峰函数,枚举峰点,O(1)转移
关于怎么看出来三分,可以是打表找规律,也可以理解成中间也许会存在一个波动最小的点。(肯定越靠边越离谱)
从minl到maxr枚举断点的位置,在计算从i-->i+1的贡献的时候,把区间分成3个部分
(1)上一步已经在左边分离(2)这一步可以恰好不再右边分离
(3)上一步和下一步都在区间包含
对于(1),贡献+
对于(2)贡献-
\(res-=(ccf[i-1]-cnr[i-1])*sumr[i-1]\)
\(res+=(ccf[i-1]-cnr[i-1]*suml[i])\)
因为重叠区间对贡献有加倍影响。
ccf差分记录相交区间个数。
点击查看代码
#include <bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int M = 1e7+10;
const long long inf = 1e18;
typedef long long ll;
inline int read(){
int x=0,f=0;char c=getchar();
while(!isdigit(c)){
if(c=='-') f=1;c=getchar();
}
do{
x=(x<<1)+(x<<3)+(c^48);
}while(isdigit(c=getchar()));
return f?-x:x;
}
ll l[M],r[M],cntr[M],tmp[M],n;
ll ans=inf;
ll ccf[M];
ll Maxr=0,Minl=inf,sumL;
ll suml[M],sumr[M];
void solve(){
ll res=0;
for(int i=1;i<n;i++){
sumL-=tmp[i];
res+=(sumL-(n-i)*tmp[i]);
}
//printf("to get:%d devote is:%d\n",Minl,res);
ans=min(ans,res);
for(int i=Minl+1;i<=Maxr;i++){
ccf[i]+=ccf[i-1];
res-=(ccf[i-1]-cntr[i-1])*suml[i];
// res-=suml[i];
//res+=sumr[i-1];
res+=(ccf[i-1]-cntr[i-1])*sumr[i-1];
// printf("chose:%d devote is:%lld\n",i,res);
ans=min(ans,res);
}
}
int main(){
double st=clock();
n=read();
for(int i=1;i<=n;i++){
l[i]=read();r[i]=read();
sumL+=l[i];
suml[l[i]]++;sumr[r[i]]++;
tmp[i]=l[i];
ccf[l[i]]++;ccf[r[i]+1]--;
cntr[r[i]]++;
Maxr=max(Maxr,r[i]);
Minl=min(Minl,l[i]);
}
for(int i=Minl;i<=Maxr;i++){
sumr[i]+=sumr[i-1];
}
for(int i=Maxr;i>=Minl;i--){
suml[i]+=suml[i+1];
}
sort(tmp+1,tmp+1+n);
solve();
while(1){
double ed=clock();
if(ed-st/CLOCKS_PER_SEC>=0.9) break;
}
printf("%lld\n",ans);
return 0;
}
三:直接三分
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#ifdef ONLINE_JUDGE
char buf[1<<21], *p1 = buf, *p2 = buf; inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
#define getchar getc
#endif
template <typename T> inline void get(T & x){
x = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while (ch >= '0' and ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar(); f and (x = -x);
} template <typename T, typename ... Args> inline void get(T & x, Args & ... _Args) { get(x); get(_Args...); }
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 3e5 + 10;
int n, mx;
pair<int,int> itv1[N], itv2[N];
int pos[N];
bool cmp1(const pair<int,int> a, const pair<int,int> b ) { return a.first < b.first; }
bool cmp2(const pair<int,int> a, const pair<int,int> b ) { return a.second < b.second; }
int check(int v) {
int ret = 0, k = 0, l = 0, sumpre = 0;
rep(i,1,n) if (itv1[i].first <= v and v <= itv1[i].second) l++;
int tot = 0;
rep(i,1,n) {
if (itv2[i].second < v) pos[++tot] = itv2[i].second, ret += l * (v - pos[tot]);
}
rep(i,1,n) {
if (itv1[i].first > v) pos[++tot] = itv1[i].first, ret += l * (pos[tot] - v);
}
rep(i,1,tot) {
ret += 1ll * (i-1) * pos[i] - sumpre;
sumpre += pos[i];
}
return ret;
}
signed main() {
get(n);
rep(i,1,n) get(itv1[i].first, itv1[i].second), mx = max(mx, itv1[i].second), itv2[i] = itv1[i];
sort(itv1+1, itv1+1+n, cmp1), sort(itv2+1, itv2+1+n, cmp2);
int l = 1, r = mx, mid1, mid2;
while ((r - l) > 2) {
// cout << l << ' ' << r << ' ' << check(mid1) << ' ' << check(mid2) << endl;
// mid1 = (l + r >> 1), mid2 = mid1 + 1;
mid1=l+(r-l)*1.0/3;mid2=r-(r-l)*1.0/3;
if (check(mid1) >= check(mid2)) l = mid1;
else r = mid2;
}
cout << min(min(check(l), check(l+1)), check(l+2)) << endl;
return 0;
}
T2【思维】给你一个01的n*n矩阵,对于ci,j位置,要么=ai要么=bj,求合法的ab组合01串个数。
暴力20tps\(O(2^n*n)\)
发现如果只枚举a,那么对于ai=1,ci,(1-j)如果有0,那么bj一定是0,所以边枚举a边删除b,最后剩下的一定就是合法的b,*2就行。
正解:
考虑枚举b序列,假设r(x)表示x行1的个数
当r(x)<cnt_b,一定存在某个位置c(x)(i)=0但是b是1,所以贡献就要由行承担,a(x)=0,那么如果a(x)(i)=1,b(i)=1
当r(x)>cnt_b,a(x)=1
当r(x)=cnt_b,当且仅仅当r(x)序列和b一一对应才能合法。
可以举例子,当b是011,r(x)是101,你发现当ax=0,第3列不合法,当ax=1,第二列不合法,总会有空缺。
所以我们从0 -- n枚举b的1的个数,从1~n行扫描矩阵,依据r(x)处理b序列,假设-1代表不确定,1代表必须是1,0代表必须是0.最后b是01-1011这种东西,考虑统计方案
首先对于a的可能性,当r(a)=cnt_b,所有的都是由b承担贡献所以答案*\(2^m\),m是相等个数。
其他两种已经确定不用枚举。
其次对于b,我们发现-1的位置可以随便填数,但是还有枚举的1的个数的限制,可能填不满,所以要从-1的位置里面选出c-cnt_1个填1。
两种只会出现一种(当你b和r一一对应,1就不可能填不满了)
考虑\(O(N^3)\)的优化,可以提前预处理出r[x]的值对应行和列对于1/0的限制
pre代表<c的前缀和,我b(i)强制取的是1,所以|传递1的需要
sub传递0的需要。
只能出现
pre:0 0 1
sub:1 0 1
不会是pre1sub0,否则矛盾(2个强制矛盾)
所以我用|=sub判断。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=5100;const ll mod=998244353;
bitset<5010>S[N],pre[N],sub[N];
vector<int>T[N];
int n;
char s[N];
int inv[N],fac[N],pw2[N],sinv[N];
inline ll C(int x,int y)
{
if(x<y||y<0)return 0;
return (ll)fac[x]*sinv[y]%mod*sinv[x-y]%mod;
}
void Pre()
{
pw2[0]=1;
_f(i,1,n)pw2[i]=(ll)pw2[i-1]*2%mod;
fac[0]=fac[1]=inv[0]=sinv[0]=inv[1]=sinv[1]=1;
_f(i,2,n)
{
fac[i]=(ll)fac[i-1]*i%mod;
inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
sinv[i]=(ll)sinv[i-1]*inv[i]%mod;
}
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,n)
{
scanf("%s",s+1);
_f(j,1,n)
{
if(s[j]=='1')S[i][j]=1;
}
T[S[i].count()].push_back(i);
}
Pre();
_f(i,1,n)
{
pre[i]=pre[i-1];
for(auto u:T[i])pre[i]|=S[u];
}
_f(i,1,n)sub[n+1][i]=1;
//if(sub[n+1][1]==1)chu("yes");
f_(i,n,1)
{
sub[i]=sub[i+1];
for(auto u:T[i])sub[i]&=S[u];
}
ll ans=0;
_f(i,0,n)
{
if((pre[i]|sub[i])==sub[i])
{
int t1=pre[i].count(),t2=sub[i].count();
ans=(ans+(ll)pw2[T[i].size()]*C(t2-t1,i-t1)%mod)%mod;
}
}
chu("%lld",ans);
return 0;
}
/*
10
0000000000
0101111001
0101111001
0101111001
0101111001
0101111001
0101111001
0101111001
0101111001
0101111001
*/
T3【dp+容斥原理(正难则反)】求长度是n,值域在[1,2 ^ n-1]之间互不重复的数的排列个数,要求^和!=0。(n<=1e7)
线性递推。考虑求出f[i]表示值域[1,2 ^ n-1]而且^是0的分配方案数。
g[i]是所有合法方案数。\(g[i]=A(2^n-1,i)\)从这么多数里面选出i个,有顺讯。
f[i-1]-->f[i]
如果前i-1个确定了,那么第i个数也是确定的,如果前i个^和是val!=0,那么ai=val。
所以f[i]=g[i-1]-f[i-1]
还有不合法情况,就是ai=之前序列里的某个数,可以化简成有i-2个数^=0的情况是f[i-2]
把那个same_val插进去,有i-1种位置,值有2 ^ n-1-(i-2)中(不可以重复),*起来就行。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=510;
const ll mod=1e9+7;
ll f[10000000+100],g[10000000+100];
int n;
inline ll qpow(ll a,ll b)
{
ll nas=1;
while(b)
{
if(b&1)nas=nas*a%mod;
a=a*a%mod;
b>>=1;
}
// chu("ns:%lld\n",nas);
return nas;
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
ll S=qpow(2,n)-1;
//chu("S:%d\n",S);
g[0]=1;
_f(i,1,n)g[i]=g[i-1]*(S-i+1+mod)%mod;
//_f(i,1,n)chu("g[%d]:%d\n",i,g[i]);
f[0]=1;
_f(i,3,n)
f[i]=(g[i-1]-f[i-1]+mod-(i-1)*(S-(i-2))%mod*f[i-2]%mod+mod)%mod;
chu("%lld",(g[n]-f[n]+mod)%mod);
return 0;
}
/*
f[i];表示前i堆石子,取了异或是0的方案书
p[i]:表示取前i堆,所有方案数
f[1]=0,p[1]
p[i]=A(2^n-1,i);
f[i]=p[i-1]-f[i-1]-f[i-2]*((2^n-1)-(i-2))*(i-1)
*/
T4[结论+DFS剪枝优化]给你一个无向图,你有x的权值可以任意分配到某几个点,\(ans=\sum{i\epsilon (u,v)}^{}val[u]*val[v]\)(n<=40)
结论:\((a+b+c)>=3根号下(a*b*c)\)取等条件就是a=b=c,好像没用?
感性理解,均分一定更优。
n=2,n=3的最优解都可以处理,考虑推广到一般情况,n>3
什么时候把权值分出去会更优,一条一条加边发现只有完全图的时候才会使得权值增加。所以找到最大完全子图就可以。
dfs剪枝
(1)可行性(2)倒序枚举(3)每次加入一个点判断是否完全连边
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int ll
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=45;//40 100
int n,m,t;
int g[45][45],P[N];
inline double getans(int u)
{
//chu("get:%d\n",u);
return ((double)t/u)*((double)t/u)*u*(u-1)/2;//有u个点的完全图贡献
}
inline void dfs(int goal,int now,int has)//目标是多少位,现在决策第几位,目前我已经有了几个点
{
// chu("goal:%d\n",goal);
if(has==goal)//选满了,一定是合法的
{
chu("%.6lf",getans(goal));exit(0);
}
if(has+n-(now-1)<goal)return;
int yes=1;
_f(i,1,has)
if(!g[P[i]][now]){yes=0;break;}
if(!yes)dfs(goal,now+1,has);
else
{
P[has+1]=now;
dfs(goal,now+1,has+1);
dfs(goal,now+1,has);//选不选都行
}
}
signed main()
{
//freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),m=re(),t=re();int sam=1;
_f(i,1,m)
{
int ui=re(),vi=re();
if(ui!=1)sam=0;
g[ui][vi]=g[vi][ui]=1;
}
_f(i,1,n)g[i][i]=1;
if(sam)
{
if(m)chu("%.6lf",((double)t/2)*(double(t)/2));
else chu("0.000000");
return 0;
}
f_(i,n,2)//枚举完全图点数
{
dfs(i,1,0);
}
return 0;
}
/*
3 2 1
1 2
2 3
*/