被claris教育了……
补题进度[5/13] 场上3题
Problem D. Euler Function
题意就是找到第k小的欧拉函数为合数的数
其实打个表就能看出来,从8开始的欧拉函数就是合数了,直接输出就行
Problem F. Grab The Tree
题意是两个人从树上任意选点,先手只能选不在一条边上的点,后手拿走剩下的点。最后选的点的异或和最大谁胜出。否则平局。
场上我是这样考虑的,如果有必胜点,那么无论如何先手都可以选择这个点。其实我们只要看最高位的个数,假设含有最高位的数的个数有奇数个,只要我选择一个就行,剩下的点就被异或掉了,假如有偶数个,那我选择次高位上的……总之最后能得出一个结论就是,先手一定会胜出。
那么平局呢?如果先手一个都不选,但是所有的点的异或和恰好是0,这样不管则么选,都只有平局和输两种可能性,所以只要判断下异或和是不是0就i行。
#include
#include
#include
#include
#include
#define ull unsigned long long
using namespace std;
const int maxn = 1e5 + 5;
vector<int> edge[maxn];
int w[maxn];
int son[maxn];
int dp[maxn][2];
void dfs(int u,int p)
{
dp[u][1] = w[u];
dp[u][0] = 0;
for(auto v : edge[u])
{
if(v == p) continue;
dfs(v,u);
son[u] = v;
dp[u][1] = max(dp[son[v]][1]^w[u],dp[u][1]);
dp[u][0] = max(dp[v][1],dp[u][0]);
}
}
int main()
{
int ca,n;
scanf("%d", & ca);
while(ca--)
{
scanf("%d",&n);
int x,y,z = 0;
for(int i = 0;iscanf("%d",&w[i]);
z ^= w[i];
}
for(int i = 0;i1;i++)
{
scanf("%d%d", &x,&y);
}
if(z == 0) printf("D\n");
else printf("Q\n");
}
return 0;
}
ps:装模做样的读入了所有的数据……
Problem L. Visual Cube
这道题是模拟,直接模拟就行了
Problem A. Ascending Rating
这道题的题意就是给出一个数列,求出长度为m的子序列中最大值和更新最大值的次数。
暴力的方法肯定是T,那么我们可以倒着考虑,用单调队列维护窗口最大值的方法就可以得出区间最大值,此时的单调队列大小就是更新的次数。
为什么队列长度是更新的次数呢?首先答案肯定是更新的方向是没有关系的,那么在更新的时候,每次队列中留下来的都是在相关区间的有序长度,也就是我们想说的变化次数。
#include
using namespace std;
#define INF 1000000007
#define ll long long
const int maxn = 1e7 + 7;
ll a[maxn];
int main()
{
int n,m,k,p,q,r,mod;
int ca;
scanf("%d", &ca);
while(ca--)
{
scanf("%d%d%d%d%d%d%d",&n,&m,&k,&p,&q,&r,&mod);
for(int i = 1;i<=k;i++)
{
scanf("%lld", &a[i]);
}
for(int i = k+1;i <= n;i++)
{
a[i] = (p * a[i-1] + (ll)q*i +r) % (ll)mod;
}
deque<int> Q;
ll A = 0,B = 0;
for(int i = n;i;i--)
{
while(!Q.empty() && a[Q.back()] <= a[i]) Q.pop_back();
Q.push_back(i);
if(i +m-1<= n)
{
while(Q.front() >= i + m) Q.pop_front();
A += a[Q.front()] ^ i*1LL;
B += i*1LL ^ (ll)Q.size();
// cout<
// cout<
}
}
printf("%lld %lld\n",A,B);
}
return 0;
}
/*
2
12 1
2
24 1
2
35 8
1
40 9
1
46 11
46 11
*/
Problem C. Dynamic Graph Matching
题意:有两种操作,一次操作添加一条边,或者删除一条边。输出每次匹配数为1,2,3,…,n/2的匹配的个数。
解法我学习了 claris 的题解,下面谈谈我自己的理解。首先我们用DP来求解,因为只有10个点。 F[i][S] F [ i ] [ S ] 代表第i次操作,当前状态为S(每个点取或者不取,雅索状态),那么加一条边相当于 F[i][S]=F[i−1][S−uv]+F[i−1][S]) F [ i ] [ S ] = F [ i − 1 ] [ S − u v ] + F [ i − 1 ] [ S ] )
其实我们只要将所有的状态从大到小更新一下,大的状态在小状态计算之前就已经计算了。就能将加入一条边的操作变成: F[S]+=F[S−uv] F [ S ] + = F [ S − u v ]
由于题目中保证了删边的合法性,那么什么时候加入这条边对最后的结果不产生影响了。所以我们直接把加边操作反过来写,从小到大进行操作。
#include
using namespace std;
#define INF 1000000007
#define ll long long
const int maxn = 1<<11;
const int mod = 1e9 + 7;
inline void add(int&a,int b)
{
a=a+bsub(int&a,int b)
{
a=a-b>=0?a-b:a-b+mod;
}
int f[2024];
int cnt[2024];
int ans[2024];
int tot;
int main()
{
int ca;
scanf("%d",&ca);
while(ca--)
{
int n,m;
scanf("%d%d", &n,&m);
tot = 1<for(int i = 0; i0;
cnt[i] = __builtin_popcount(i);
}
f[0] = 1;
while(m--)
{
char op[10];
int u,v;
scanf("%s%d%d",op,&u,&v);
u--,v--;
int s = (1<1<if(op[0] == '+')
{
for(int i = tot-1 ; ~i; i--)
{
if(!(i & s))
add(f[i^s],f[i]);
}
}
else
{
for(int i = 0; iif(!(i & s))
sub(f[i^s],f[i]);
}
}
for(int i = 1; i<=n; i++) ans[i] = 0;
for(int i = 1; ifor(int i = 2; i<=n; i+= 2)
{
printf("%d%c",ans[i],i < n ? ' ' : '\n' );
}
}
}
return 0;
}
/*
1
4 8
+ 1 2
+ 3 4
+ 1 3
+ 2 4
- 1 2
- 3 4
+ 1 2
+ 3 4
*/