hdu 多校赛 第二场
slove 3/12
rank 224
补题 6/12
---------------------------------------------------
hdu 6595
http://acm.hdu.edu.cn/showproblem.php?pid=6595
题意:给一个N,随机一个随机数 n 在[1,N]中,产生长度是[1,n]的全排列,然后输出题目给的函数的期望。
------------------------------------------------
1、题目给的函数一个是求他的子序列的,一个是计算这个序列的逆序数的
2、假设确定n,然后枚举每一种状态,然后会发现规律,对于每一个确定的n,他的期望等于 每个排列的期望逆序数
3、也就等于,这里的sum就是 n 的全排列的逆序对对数总和。
4、打表发现 sum 是由规律的,
5、所以如果确定 n 的值的话,他的期望答案就是
6、题目要求的是先选 n ,所以答案就是,前 n 项求和后答案是
Code :
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 998244353;
ll power(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll a[3005];
int main()
{
ll n;
ll inv3 = power(3, mod - 2);
a[0] = 0;
for (ll i = 1; i < 3005; i++)
{
a[i] = i * (i - 1) % mod * inv3 % mod;
a[i] = (a[i] + a[i - 1]) % mod;
}
while (scanf("%lld", &n) > 0)
{
printf("%lld\n", a[n] * power(n, mod - 2) % mod);
}
}
hdu 6598
http://acm.hdu.edu.cn/showproblem.php?pid=6598
题意:
有n个士兵,他们之间有m种联系
以下m行输入五个数,u,v,A,B,C.
u,v表示两个有联系的士兵,A表示两个士兵都是法师的战斗力,
B表示两个士兵一个是法师一个是战士的战斗力
C表示两个士兵都是战士的战斗力
现让你分配所有士兵的职业以达到最大战力
---------------------------------------------------------------------
题解:
首先我们考虑一条联系,可以得到一个最小割模型,建一个超级源点和汇点,然后u,v各建一个顶点.
源点,汇点和节点之间连有边,u,v节点之间有一条双向边.
源点与节点之间的边为a,b,汇点与节点之间的边为c,d,节点之间的边为e
由最小割的定义:求最小代价让源点汇点不连通,然后u,v的阵营也分出来了,要不在源点这边,
要不在汇点这边,假设说在源点这边的为士兵,汇点那边的为法师.
容易得到一个方程组
a+b=A,c+d=C,a+d+e=B,b+c+e=B
五个未知数,四个方程,求不出具体值,但是只要代一组解进去建图就OK了
然后考虑题目要求的是最大代价,有总贡献D=A+B+C
方程组变为
a+b=D-A,c+d=D-C,a+d+e=D-B,b+c+e=D-B.
化简得到
a+b=B+C,c+d=A+B,a+d+e=A+C,b+c+e=A+C.
求得的a,b,c,d,e答案就不写了,合法就行
这样求出来的最小割,再用总贡献-当前求出的最小割即为最大代价了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e6+5;
const ll INF = 0x3f3f3f3f;
int n,m,s,t,u[MAX],v[MAX],first[MAX],nextt[MAX],cur[MAX],cnt=0,deep[MAX];
ll w1[MAX],w2[MAX],ans=0,wa[MAX],wb[MAX],wc[MAX],w[MAX];
bool vis[MAX];
void add(int a,int b,int c){
u[cnt]=a,v[cnt]=b,w[cnt]=c;
nextt[cnt]=first[u[cnt]];
first[u[cnt]]=cnt;++cnt;
}
bool bfs(){
deque<int>list1;
list1.push_back(s);
memset(deep,0x3f,sizeof(deep));
deep[s]=0;
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;++i) cur[i]=first[i];
vis[s]=true;
while(!list1.empty()){
int now = list1.front();list1.pop_front();
for(int num = first[now];num!=-1;num=nextt[num]){
if(!vis[v[num]]&&w[num]>0){
vis[v[num]]=true;
deep[v[num]]=deep[now]+1;
list1.push_back(v[num]);
}
}
}
return deep[t]!=INF;
}
int dfs(int now,ll limit){
if(!limit||now==t) return limit;
int flow=0,length=0;
for(int num = first[now];num!=-1;num=nextt[num]){
cur[now] = num;
if(deep[v[num]]==deep[now]+1 && (length = dfs(v[num],min(limit,w[num])))){
flow+=length;
w[num]-=length;
w[num^1]+=length;
limit-=length;
if(!limit) break;
}
}
return flow;
}
ll dinic(){
ll now=0;
while(bfs()) now+=dfs(s,INF);
return now;
}
void solve(){
while(~scanf("%d%d",&n,&m)){
s=n+1,t=n+2,ans=0;
memset(first,-1,sizeof(first));
memset(w1,0,sizeof(w1));
memset(w2,0,sizeof(w2));
for(int i=0;i!=m;++i){
int a,b;
ll c,d,e;
scanf("%d%d%lld%lld%lld",&a,&b,&c,&d,&e);
w1[a]+=d+e,w1[b]+=d+e,w2[a]+=c+d,w2[b]+=c+d;
add(a,b,c+e-2*d),add(b,a,0),add(b,a,c+e-2*d),add(a,b,0);
ans+=c+d+e;
}
for(int i=1;i<=n;++i) add(s,i,w1[i]),add(i,s,0),add(i,t,w2[i]),add(t,i,0);
printf("%lld\n",ans-dinic()/2);
}
}
int main(void)
{
solve();
return 0;
}
hdu 6599
http://acm.hdu.edu.cn/showproblem.php?pid=6599
题意:给出一个字符串,求长度为1-n的合法的回文串的个数,合法的意思是当 是回文串并且也是回文串
----------------------------------------------
做法:用回文树求出本质相同的字符串是否合法如果合法就就直接加入,不合法就跳过,这里判断是否合法我用的是马拉车,然后分成判断的区间是奇数还是偶数
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 3e5 + 10;
const int MAXN = 26;
int nex[N][MAXN], fail[N];
ll cnt[N];
int num[N], len[N], s[N], q[N << 1]; /*s用来存放已经插入的字符*/
char str[N]; /*cnt 以i结尾的回文串出现次数 num 以i结尾的回文串种类*/
int last, idx, length;
ll ans[N];
int id[N], r[N<<1];
void newnode(int l)
{
len[idx] = l;
memset(nex[idx], 0, sizeof(nex[idx]));
}
void init()
{
idx = 1, last = 0;
len[0] = 0, len[1] = -1;
cnt[1] = cnt[0] = 0;
num[0] = num[1] = 0;
memset(nex[0], 0, sizeof(nex[0]));
memset(nex[1], 0, sizeof(nex[1]));
length = 0;
s[length] = -1;
fail[0] = 1;
}
int get_fail(int x)
{
while (s[length - len[x] - 1] != s[length])
x = fail[x];
return x;
}
void insert_pam(int c)
{
s[++length] = c;
int p = get_fail(last);
if (!nex[p][c])
{
++idx;
id[idx] = length;
newnode(len[p] + 2);
fail[idx] = nex[get_fail(fail[p])][c];
nex[p][c] = idx;
num[idx] = num[fail[idx]] + 1;
}
last = nex[p][c];
cnt[last]++;
}
void count()
{
for (int i = idx; i >= 2; i--)
cnt[fail[i]] += cnt[i];
}
void Manacher() {
q[0] = '$'; //len字符串原本长度
int n = strlen(str + 1);
for (int i = 1; i <= n; i++)
{
q[2 * i - 1] = '#';
q[2 * i] = str[i];
r[2 * i - 1] = r[2 * i] = 0;
}
n = n * 2;
int mxr = 0, mid = 0;//mxr为目前最长回文子串的右端点 mid为mxr对应回文子串的中点
for (int i = 1; i <= n; i++)
{
if (i < mxr)//该点被回文子串包含
r[i] = min(mxr - i, r[2 * mid - i]);
else
r[i] = 1;
while (q[i - r[i]] == q[i + r[i]])//扩展
{
r[i]++;
if (r[i] + i > mxr)
{
mxr = r[i] + i;
mid = i;
}
}
}
}
bool check(int x)
{
int l = id[x] * 2;
int k = (len[x] + 1) / 2;
l = l - (k - 1);
if (r[l] >= k)
return true;
return false;
}
int main()
{
while (~scanf("%s", str + 1))
{
init();
memset(cnt, 0, sizeof(cnt));
int n = strlen(str + 1);
memset(ans, 0, sizeof(ans));
for (int i = 1; i <= n; i++)
insert_pam(str[i] - 'a');
count();
Manacher();
for (int i = 2; i <= idx; i++)
{
if (check(i))
ans[len[i]] += cnt[i];
}
for (int i = 1; i <= n; i++)
{
printf(i == n ? "%lld\n" : "%lld ", ans[i]);
}
}
}
hdu 6600
http://acm.hdu.edu.cn/showproblem.php?pid=6600
题意:有一个 n 位数 x ,让你给一个数字询问,问最少问多少次能知道 x 的具体的值。输出最少次数的方案数
-----------------------------------------------
枚举每一位,所以查询的次数是 ,注意这里要模1e6+3,所以所有大于等于1e6+3的阶乘都是0。
slove by all
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAX = 1e6 + 3;
const ll mod = 1e6 + 3;
ll ans[MAX];
void init() {
ll sum = 1;
for (ll i = 1; i < MAX; ++i) {
sum *= i;
sum %= mod;
ans[i] = sum;
}
}
void solve() {
ll n;
init();
while (~scanf("%lld", &n)) {
if (n >= MAX) printf("0\n");
else printf("%lld\n", ans[n]);
}
}
int main(void)
{
solve();
return 0;
}
hdu 6601
http://acm.hdu.edu.cn/showproblem.php?pid=6601
题意:给一个长度为 n 的序列,q 次询问,每次询问区间中能构成的最大三角形。
-------------------------------------------
1、每次查询最多查询45次,就能找到合法答案
2、理由是假设区间中所有数字都是斐波那契数列,第45项大于1e9,所以在这45个数字中,一定有可以构成三角形的三条边
3、主席树找区间第 k 大,然后暴力更新答案,复杂度
Code :
int main()
{
while (scanf("%d%d", &n, &m) > 0)
{
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
t[i - 1] = a[i];
}
sort(t, t + n);
cnt = unique(t, t + n) - t;
for (int i = 1; i <= n; i++)
{
int tt = lower_bound(t, t + cnt, a[i]) - t + 1;
fp[tt] = a[i];
a[i] = tt;
}
init();
for (int i = 1; i <= n; i++)
{
root[i] = root[i - 1];
build(a[i], root[i], 1, n);
}
int ql, qr, k;
while (m--)
{
maxn1 = 0; maxn2 = 0;
scanf("%d%d", &ql, &qr);
int len = qr - ql + 1;
if (len <= 2)
{
printf("-1\n");
continue;
}
maxn1 = query(root[qr], root[ql - 1], len, 1, n);
maxn2 = query(root[qr], root[ql - 1], len - 1, 1, n);
for (int k = len - 2; k >= 1; k--)
{
int ans = query(root[qr], root[ql - 1], k, 1, n);
if (fp[maxn1] < fp[maxn2] + fp[ans])
{
printf("%lld\n", fp[maxn1] + fp[maxn2] + fp[ans]);
goto qwe;
}
else
{
maxn1 = maxn2;
maxn2 = ans;
}
}
printf("-1\n");
qwe:;
}
}
}
hdu 6602
http://acm.hdu.edu.cn/showproblem.php?pid=6602
题意:n个数字,所有数字的大小在1-C之间,定义好的区间为要么某个数字在区间中出现的次数大于等于k,要么没有出现,求好的区间的最大长度。
1、我们定义一个数字对于一个区间合法为这个数字在这个区间出现次数大于等于k,或者等于0。
2、考虑线段树,维护以当前遍历到的点为右端点,其他点为左端点的这些区间对于多少个数字合法。
3、所以题目就变成了求最左端的值为C的下标。
4、每次更新右端点时
a、以当前这个点为左端点的区间,对于除当前数字外其他所有数字合法,所以这段区间是C-1
b、当前这个点和上一次出现这个点的值之间的所有值,在没有遍历到当前的这个点时,对于当前的数字,都是计算过贡献的,但现在右端点取了这个数字,所以这个数字不一定再对于以中间的这些点为左端点的区间成立,所以中间这一段需要减一。
c、如果当前数字的出现次数大于k,那么说明之前有一段区间对于当前数字又合法了,所以前面的那段区间加一。
5、线段树修改:最大值和标记同时加上val
6、线段树查询:查询值为C 的最靠左边的下标,没有返回-1。
7、线段树查询不能直接查(也可能是本蒟蒻不会),要先打一个标记,记录当前区间最大值的下标,然后区间合并的时候更新一下下标。
Code:
#include <bits/stdc++.h>
#define lson left,mid,k<<1
#define rson mid+1,right,k<<1|1
#define imid int mid=(left+right)/2;
using namespace std;
const int MAXN = 1e5 + 5;
struct node
{
int l;
int r;
int maxn;
int mark;
int pos;
}que[MAXN * 4];
int C, n, k, ql, qr, val;
void up(int k)
{
que[k].maxn = max(que[k << 1].maxn, que[k << 1 | 1].maxn);
que[k].pos = (que[k << 1].maxn >= que[k << 1 | 1].maxn) ? que[k << 1].pos : que[k << 1 | 1].pos;
}
void down(int k)
{
if (que[k].mark)
{
que[k << 1].mark += que[k].mark;
que[k << 1 | 1].mark += que[k].mark;
que[k << 1].maxn += que[k].mark;
que[k << 1 | 1].maxn += que[k].mark;
que[k].mark = 0;
}
}
void build(int left = 1, int right = n, int k = 1)
{
que[k].l = left;
que[k].r = right;
que[k].maxn = 0;
que[k].mark = 0;
if (left == right)
{
que[k].pos = left;
return;
}
imid;
build(lson);
build(rson);
}
void update(int left = 1, int right = n, int k = 1)
{
if (qr < left || right < ql)
return;
if (ql <= left && right <= qr)
{
que[k].maxn += val;
que[k].mark += val;
return;
}
down(k);
imid;
update(lson);
update(rson);
up(k);
}
int query(int left = 1, int right = n, int k = 1)
{
if (que[k].maxn != C)
return -1;
if (ql <= left && right <= qr)
return que[k].pos;
imid;
if (ql <= mid)
{
int t = query(lson);
if (t != -1)
return t;
}
if (qr > mid)
{
int t = query(rson);
if (t != -1)
return t;
}
//return -1;
}
vector<int>v[100005];
int main()
{
while (scanf("%d%d%d", &n, &C, &k) > 0)
{
for (int i = 1; i <= C; i++)
{
v[i].clear();
v[i].push_back(0);
}
build();
int ans = 0, t;
for (int i = 1; i <= n; i++)
{
scanf("%d", &t);
ql = qr = i; val = C - 1;
update();
if (!v[t].empty())
{
ql = v[t].back() + 1; qr = i - 1; val = -1;
update();
}
v[t].push_back(i);
if (v[t].size() > k)
{
ql = v[t][v[t].size() - k - 1] + 1; qr = v[t][v[t].size() - k]; val = 1;
update();
}
ql = 1, qr = i;
t = query();
if (t != -1)
ans = max(ans, i - t + 1);
}
printf("%d\n", ans);
}
}