雅礼学习10.6

雅礼学习10.6

上午考试

各题状况

T1

二分答案

应该有反例,就是说,答案应该不是单调的

但是不会写其他的算法了啊。。。

T2

我TM。。。

图片.png

第二个红框圈出来的部分应该是

if(x1+x1!=s)

写错了,就没了\(18\)分。。

T3

写了个\(n^4\)的暴力

最后发现题目中的矩形的四个顶点不一定是给定的顶点。。

那就GG了

各题题目及考场代码

T1

图片.png

图片.png

/*
 * 二分答案。。
 * 复杂度O(20(N+NlogN+M))的,感觉很悬
 * 排序应该可以优化掉,但是不太会哎。
 */
#include <cstdio>
#include <algorithm>

inline int read()
{
	int n=0,w=1;register char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0' && c<='9')n=n*10+c-'0',c=getchar();
	return n*w;
}

const int N=1e6+1;
int n,m;
struct Seg{
	int b,k;
}seg[N];
long long S,emp[N];

inline bool judge(long long x)
{
	for(int i=1;i<=n;++i)
		emp[i]=seg[i].k*x+seg[i].b;
	std::sort(emp+1,emp+1+n);
/*
	for(int i=1;i<=n;++i)
		printf("%lld ",emp[i]);
	puts("");
*/
	long long res=0;
	for(int i=0;i<m;++i)
	{
		if(emp[n-i]<0)break;
		res+=emp[n-i];
	}
//	printf("%lld\n",res);
	return res>=S;
}

int main()
{
	freopen("merchant.in","r",stdin);
	freopen("merchant.out","w",stdout);

	n=read(),m=read();
	scanf("%lld",&S);
//	printf("%d %d %lld\n",n,m,S);
	for(int i=1;i<=n;++i)
	{
		seg[i].k=read(),seg[i].b=read();
//		printf("%d %d\n",seg[i].k,seg[i].b);
	}
	int l=0,r=1e9,mid;
	while(l<=r)
	{
		mid=l+r>>1;
		if(judge(mid))
			r=mid-1;
		else	l=mid+1;
	}
	printf("%d",l);

	fclose(stdin),fclose(stdout);
	return 0;
}

T2

图片.png
图片.png

/*
 * 题目描述什么鬼啊。。。完全看不懂
 *
 * emmmm
 * 高斯消元么。。。
 */
#include <cstdio>
#include <algorithm>
inline int read()
{
	int n=0,w=1;register char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
	return n*w;
}
const int N=1e6+1;
int n,q,fa[N],w[N];
/*
const double eps=1e-8;
double a[N][N];

int sign(double x)
{//判断一个数是正数、负数还是0
    if(fabs(x)<=eps)return 0;//fabs是对浮点数取绝对值
    if(x>0)return 1;
    return -1;
}

inline void GG(int x)
{
	if(a[x][n+1])
		puts("none");
	else	puts("inf");
}

inline void solve()
{
	for(int i=1;i<=n;++i)
	    a[i][n+1]=b[i];//得到扩展矩阵
	
	for(int i=1;i<=n;++i)
	{
		int p=i;
		for(int j=i+1;j<=n;++j)
			if(fabs(a[j][i]>fabs(a[p][i])))
				p=j;
		for(int j=1;j<=n+1;++j)
			swap(a[p][j],a[i][j]);
		if(sign(a[i][i])==0)
			GG(i);
		for(int j=i+1;j<=n;++j)
		{
			double ratio=a[j][i]/a[i][i];//计算是多少倍
			for(int k=1;k<=n+1;++k)
				a[j][k]=a[j][k]-ratio*a[i][k];
		}
		for(int i=n;i>0;--i)
		{
			for(int j=i+1;j<=n;++j)
				a[i][n+1]=a[i][n+1]-x[j]*a[i][j];
			x[i]=a[i][n+1]/a[i][i];
		}
}

int main()
{
	freopen("equation.in","r",stdin);
	freopen("equation.out","w",stdout);
	n=read(),q=read();
	for(int i=2;i<=n;++i)
		fa[i]=read(),w[i]=read();
	int type,u,v,s;
	while(q--)
	{
		type=read(),u=read(),v=read();
		if(type==1)
		{
			s=read();
			solve();
		}
		else
		{
			w[u]=v;
			a[u][fa[u]]=v;
		}
	}
	fclose(stdin);fclose(stdout);
	return 0;
}
日。。
写挂了
*/
int main()
{
	freopen("equation.in","r",stdin);
	freopen("equation.out","w",stdout);
	n=read(),q=read();
	for(int i=2;i<=n;++i)
		fa[i]=read(),w[i]=read();
	int type,u,v,s;
	int x2,x1;
	if(n==2)
	{
		while(q--)
		{
			type=read(),u=read(),v=read();
			if(type==1)
			{
				s=read();
				if(u==v && v==2)
				{
					x2=s/2;
					if(x2+x2!=s)
						puts("none");
					else	printf("%d\n",w[2]-x2);
				}
				else
					if(u==v && v==1)
					{
						x1=s/2;
						if(x2+x2!=s)
							puts("none");
						else	printf("%d\n",x1);
					}
					else
					{
						if(s==w[2])
							puts("inf");
						else	puts("none");
					}
			}
			else	w[u]=v;
		}
	}
	else
	{
		while(q--)
		{
			srand(19260817);
			while(q--)
			{
				int x=rand();
				if(x%3==1)
					puts("none");
				else
					if(x%3==2)
						puts("inf");
					else	printf("%d\n",x);
			}
		}
	}
	fclose(stdin);fclose(stdout);
	return 0;
}

T3

图片.png
图片.png

#include <cstdio>
#include <map>

const int N=2501,mod=1e9+7;
std::map<int,bool> mp1,mp2;
int n,t1,t2,lx,ly,rx,ry;
int ans,x[N],y[N];

inline int read()
{
	int n=0,w=1;register char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0' && c<='9')n=n*10+c-'0',c=getchar();
	return n*w;
}

inline int min(int x,int y)
{return x<y?x:y;}
inline int max(int x,int y)
{return x>y?x:y;}

int main()
{
    freopen("rectangle.in","r",stdin);
	freopen("rectangle.out","w",stdout);
	n=read();
    for(int i=1;i<=n;++i)
        x[i]=read(),y[i]=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            for(int q=1;q<=n;++q)
                for(int p=1;p<=n;++p)
				{
                    if(i==j&&j==q&&q==p)continue;
                    lx=min(min(x[i],x[j]),min(x[q],x[p]));
                    ly=min(min(y[i],y[j]),min(y[q],y[p]));
                    rx=max(max(x[i],x[j]),max(x[q],x[p]));
                    ry=max(max(y[i],y[j]),max(y[q],y[p]));
                    t1=(lx*ry+ly*rx);
                    t2=(lx*rx+ly*ry);
                    if(mp1[t1]==0&&mp2[t2]==0)
					{
                        ans=(ans+(rx-lx)*(ry-ly))%mod;
                        mp1[t1]=mp2[t2]=1;
                    }
                }
	printf("%d",ans);
	fclose(stdin);fclose(stdout);
    return 0;
}

正解及代码

T1

选择任意一个集合,得到的收益和都可以表示为一个一次函数的形式。

我们只关心这些一次函数的最大值,可以发现这个最大值一定是先降后增、单调递增或者单调递减。

图片.png

因此我们只需要\(check\)一下\(0\)时刻是否符合条件,如果不符合则进行二分。 注意\(check\)的时候我们只需要找出最大的\(m\)个即可,因此可以\(O(n)\)地做,具体做法是快排的过程中只递归一边,或者直接用\(STL\)\(nth_element()\)即可

#include <bits/stdc++.h>

#define For(i, j, k) for (int i = j; i <= k; i++)

using namespace std;

const int N = 1e6 + 10;

typedef long long LL;

int n, m;
LL S;

int k[N], b[N];
LL val[N];

bool check(int x) {
    For(i, 1, n) val[i] = 1ll * k[i] * x + b[i];
    nth_element(val + 1, val + m, val + n + 1, greater<LL>());
    LL sum = 0;
    For(i, 1, m) if (val[i] > 0 && (sum += val[i]) >= S) return true;
    return sum >= S;
}

int main() {

    scanf("%d%d%lld", &n, &m, &S);
    For(i, 1, n) scanf("%d%d", &k[i], &b[i]);

    if (check(0)) { puts("0"); return 0; }

    int L = 1, R = 1e9;
    while (L < R) {
        int mid = (L + R) / 2;
        if (check(mid)) R = mid;
        else L = mid + 1;
    }
    printf("%d\n", L);

    return 0;
}

T2

每个变量都可以表示成\(x_i=k+x_1\)或者\(x_i=k−x_1\)的形式,表示为这个形式之后就可以方便地回答询问了。

对于询问\(1\),只需要将表示\(u\)\(v\)的式子加起来,这时会出现两种情况:要么会得到\(x_u+x_v=t\)的形式,此时只需要判断是否有\(s=t\);要么会得到\(x_u+x_v=t+2x_1\)\(x_u+x_v=t−2x_1\),此时可以解出\(x_1\),注意判断解是否是整数即可。

对于修改操作,实际上是修改一个子树内的变量的\(k\),这里可以将深度为奇数和偶数的点分开考虑,不难发现就是区间加减。由于只需要单点询问,用一个树状数组维护即可

\(O((n+q)\log n)​\)

#include <bits/stdc++.h>

#define getchar getchar_unlocked
#define For(i, j, k) for (int i = j; i <= k; i++)

using namespace std;

int Read() {
    char c = getchar(); int x = 0;
    int sig = 1;
    while (c < '0' || c > '9') { if (c == '-') sig = -1; c = getchar(); }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * sig;
}

const int N = 1e6 + 10;

int n, q;

struct Binary_Indexed_Tree {

    int c[N];

    inline int lowbit(int x) {
        return x & (-x);
    }

    void add(int x, int w) {
        while (x <= n) {
            c[x] += w;
            x += lowbit(x);
        }
    }

    int sum(int x) {
        int ret = 0;
        while (x) {
            ret += c[x];
            x -= lowbit(x);
        }
        return ret;
    }

}T;

int dfn[N], rdfn[N], dep[N];
int fa[N], w[N];
vector<int> G[N];

void DFS_init(int o) {
    static int clk = 0;
    dfn[o] = ++clk;
    for (int v : G[o]) dep[v] = dep[o] ^ 1, DFS_init(v);
    rdfn[o] = clk;
}

int main() {

    n = Read(), q = Read();
    For(i, 2, n) fa[i] = Read(), w[i] = Read(), G[fa[i]].push_back(i);
    DFS_init(1);
    For(i, 2, n) if (!dep[i]) w[i] = -w[i];
    For(i, 2, n) T.add(dfn[i], w[i]), T.add(rdfn[i] + 1, -w[i]);

    while (q--) {
        int op = Read();
        if (op == 1) {
            int u = Read(), v = Read(), s = Read();
            int x = T.sum(dfn[u]), y = T.sum(dfn[v]);

            if (dep[u] && dep[v]) {
                long long rt = 1ll * x + y - s;
                if (rt % 2) puts("none");
                else printf("%lld\n", rt / 2);
            } else if (!dep[u] && !dep[v]) {
                long long rt = 1ll * x + y + s;
                if (rt % 2) puts("none");
                else printf("%lld\n", rt / 2);
            } else {
                if (dep[v]) swap(u, v), swap(x, y);
                if (x - y == s) puts("inf");
                else puts("none");
            }

        } else {
            int u = Read(), nw = Read();
            if (!dep[u]) nw = -nw;
            T.add(dfn[u], nw - w[u]), T.add(rdfn[u] + 1, w[u] - nw);
            w[u] = nw;
        }
    }

    return 0;
}

T3

#include <bits/stdc++.h>

#define For(i, j, k) for (int i = j; i <= k; i++)
#define Forr(i, j, k) for (int i = j; i >= k; i--)

using namespace std;

const int N = 2510;
const int Mod = 1e9 + 7;

int n, m = 2500;

struct Binary_Indexed_Tree {

    int c[N];

    void init() {
        For(i, 1, m) c[i] = 0;
    }

    int lowbit(int x) { return x & (-x); }

    void add(int x, int w) {
        for (; x <= m; x += lowbit(x)) c[x] += w;
    }

    int sum(int x) {
        int ret = 0;
        for (; x; x -= lowbit(x)) ret += c[x];
        return ret;
    }

}T, S;

int pos[N][N];
int c[N];
bool vis[N];

void upd(int x) {
    if (vis[x]) return;
    vis[x] = true;
    T.add(x, 1), S.add(x, x);
}

int main() {

    scanf("%d", &n);
    For(i, 1, n) {
        int x, y;
        scanf("%d%d", &x, &y);
        pos[x][++c[x]] = y;
    }

    For(i, 1, m) sort(pos[i] + 1, pos[i] + c[i] + 1), pos[i][c[i] + 1] = m + 1;

    int ans = 0;
    For(i, 1, m) if (c[i]) {
        T.init(), S.init();
        For(j, 1, m) vis[j] = false;
        For(j, 1, c[i]) upd(pos[i][j]);
        Forr(j, i - 1, 1) if (c[j]) {
            int pa = 1, pb = 1, cur = max(pos[i][1], pos[j][1]);
            For(k, 1, c[j]) upd(pos[j][k]);
            while (pos[i][pa + 1] <= cur) ++pa;
            while (pos[j][pb + 1] <= cur) ++pb;

            while (pa <= c[i] && pb <= c[j]) {
                int nxt = min(pos[i][pa + 1], pos[j][pb + 1]), L = min(pos[i][pa], pos[j][pb]);
                ans = (ans + (1ll * (S.sum(nxt - 1) - S.sum(cur - 1)) * T.sum(L) 
                            - 1ll * (T.sum(nxt - 1) - T.sum(cur - 1)) * S.sum(L)) * (i - j)) % Mod;
                cur = nxt;
                if (pos[i][pa + 1] <= cur) ++pa;
                if (pos[j][pb + 1] <= cur) ++pb;
            }
        }
    }

    printf("%d\n", ans);

    return 0;
}

下午讲课:DP选讲

例1

一个长度为\(n\)的序列\(A\),我们称一个元素是好的,当且仅当它严格大于相邻的元素。你可以进行若干次操作,每次将一个元素减小\(1\)
对于每个\(k\in [1,\lceil\frac{n}{2}\rceil]\)求至少要进行多少次操作使得序列中至少有\(k\)个好的元素
\(n\le 5000,A_i\le 10^5\)

解:
稍作观察发现,如果最终方案中一个位置是好的,那我们一定不会对它做操作;如果不是,它最终的值是\(A_{i-1}-1,A_i,A_{i+1}-1\)中的一个

\(dp[i][j][0]\)表示前\(i\)个元素有\(j\)个是好的,且已经钦定\(A_i\)是好的,此
时对前\(i\)个元素至少要进行的操作次数。转移到\(i+1\)时需要确保操作后\(A_{i+1}\lt A_i\)
类似地,\(dp[i][j][1/2/3]\)表示\(A_i\)不是好的时的三种情况
\(O(n^2)\)

例2

一个长度为\(n\)的序列\(A\),定义一个1到n的排列p是合法的,当且仅当\(\forall i\in[1,n-1].A_{p_i}\times A_{p_{i+1}}\)不是完全平方数
求有多少合法的排列,对\(1e9+7\)取模
\(n\le 300,A_i\le 10^9\)

解:
对于每个元素去掉它的平方质因子,问题转化为有多少排列\(p\)满足\(\forall i\in[1,n-1],A_{p_i}\ne A_{p_{i+1}}\),即相邻元素不同
先统计有多少种不同的元素,以及每种元素的个数。考虑每次将值相同的所有元素加入排列后的序列
\(dp[i][j]\)表示已经将前\(i\)种元素加入序列,有\(j\)对相邻位置相同的方案数。转移时枚举将第\(i+1\)种元素加入后会将多少对原来不合法的相邻位置拆开,以及会新增多少不合法的相邻位置即可。
总元素个数\(O(n)\),因此复杂度最坏\(O(n^3)\)

例3

有一个长度为\(n\)的序列\(A\)和常数\(L,P\),你需要将它分成若干段,每一段的代价为\(|(\sum A_i)-L|^P\),求最小代价的划分方案

\(n\le 10^5,1\le P\le 10\)

解:

\[dp[j]=\min_{i=0}^{j-1}|sum_j-sum_i-L|^P+dp[i] \]

这个方程具有决策单调性,即\(\forall u\lt v\lt i\lt j\),若在\(i\)处决策\(v\)优于决策\(u\),则在\(j\)处必有\(v\)优于决策\(u\)

用一个栈维护每个决策更新的区间,新加入一个决策时可以二分得到它的区间

\(O(n\log n)\)

证明:

本题的证明需要讨论绝对值符号,先考虑里面的值为正的情况,其余的情况类似。

定义\(f_i(x)=dp[i]+(sum_x-sum_i-L)^P\),只需证明\(g(x)=f_u(x)-f_v(x),u\lt v\lt x\)单调增

也就是随着\(x\)增大决策\(u\)相较于决策\(v\)越来越不优

\[g(x)=(sum_x-sum_u)^P-(sum_x-sum_v)^P+dp[u]-dp[v]\\ g'(x)=P(sum_x-sum_u)^{P-1}-P(sum_x-sum_v)^{P-1}\\ g'(x)\ge 0 \]

例4

一张\(n\)个点\(m\)条边的带权有向图,每条边的长度都为\(1\)。求一条最长的路径,满足边权严格递增,且路径上边的顺序与输入中这些边的相对顺序相同。

\(n,m\le 10^5\)

解:

按输入顺序考虑每一条边。设\(dp[i][j]\)表示路径到了节点\(i\),上一条边的权值\(j\)时的最长长度。

显然有用的状态是\(O(m)\)的。对每个点维护一个\(set\)来存这些状态以及它们的\(dp\)值,并保证\(dp\)值是随\(j\)递增的。这样加入一条边\((u,v)\)时,只需要在节点\(u\)\(set\)上二分就可以进行转移了。同时还需要维护\(v\)\(set\)
\(O(n+m\log m)\)

例5

有一棵\(n\)个点的带权二叉树(不知道树的形态),给出对这棵二叉树进行中序遍历得到的权值序列,判断是否存在与之相符的一棵二叉树,树上每对相邻节点权值的\(gcd\)大于\(1\)

\(n\le 700,w_i\le 10^9\)

对于二叉树的一棵子树,其中序遍历是一段连续的区间。一个想法(跟昨天的题目很像)是设\(dp[l][r][i]\)表示是否能将区间\([l,r]\)建成一棵以\(i\)为根的合法二叉树,转移枚举两边子树的根,但这样的复杂度是\(O(n^5)\)

注意到对于区间\([l,r]\)构成的二叉树,除非\(l=1,r=n\),否则它一定是\(r+1\)的左子树,或者\(l-1\)的右子树。因此我们只关心根节点与\(l-1\)\(r+1\)的权值的\(gcd\)是否为\(1\),而不需要知道根是哪个节点。

于是状态数变为\(O(n^2)\),转移仍然枚举根即可,\(O(n^3)\)

posted @ 2018-10-11 19:30  快乐永恒  阅读(184)  评论(2编辑  收藏  举报